;--[[ ; since we include a Lua script at the end, the top half is placed in a Lua block comment ; ; Example synSpace:Drone Runners StarMap ; ; synSpace is an Arcadian toy, and now, an Android game, for up to 8 players. ; For more information, please visit http://www.synthetic-reality.com ; ; Windows Version (c) 2001 synthetic-reality.com all rights reserved ; Android Version (c) 2013 synthetic-reality.com all rights reserved ; ; ----- ; ; "Slaves of Improovium" ; ; (c) 2016 Dan Samuel, synthetic-reality.com, all rights reserved ; ; This particular starmap is both the tutorial and the API development/demo ; map. You are free to re-use it, in whole or in part, for your own starmaps, ; used with synSpace: Drone Runners. ; ; For purpose of this document, a starmap describes a 'star group' somewhere in ; our galaxy. It's a big galaxy, bigger than a single starmap. This document ; occasionally will say 'galaxy' where it ought to say 'star group'. ; ; The starmap describes a square 8192 units on a side. ; ; The 'visible grid lines' are spaced at 256 unit intervals. ; ; The (x, y) coordinate system looks like this: (a 'top' view of space) ; ; (0, 8191) (8191,8191) ; +---------N---------+ ; | | ; | | ; | | ; | | ; W o E ; | | ; | | ; | | ; | | ; +---------S---------+ ; (0, 0) (8191, 0) ; ; NOTE: barriers too close to the edge of the map will not work as expected. ; keep them at least 20 units from edge. ; ; In general, avoid the edges of the map. In specific, avoid negative coordinates ; as many tables will treat that as a command to "generate a random value" ; ; From the player's perspective, the map WRAPS when you cross an edge. ; ; Map files have two parts, a mandatory TOP HALF that contains a static description of a ; star system, and an optional BOTTOM HALF that includes whatever Lua scripting ; you feel like adding for a more dynamic environment. ; ; Things your map file TOP HALF can control: ; ; * Barriers (bouncy, transparent, hurtful, or healing) ; * Colors of barriers ; * Pain levels associated with barriers ; * Player "New Ship" locations ; * Power Up spawn locations and periods ; * Gravity Objects (suns, planets, black holes) ; * Rectangular Zones with special properties ; ; Things your optional BOTTOM HALF Lua Scripting can control ; ; * dynamically modify most of the settings from the top half ; * state machine based scenes, bots, and games of your own design ; * i.e. missions, quests, adventures ; * these can be solo, or multiplayer, with your copy of the ; script talking to other players copies of their scripts. ; ; --------------------------------------------------------------------------- ; ; START OF TOP HALF ; ; The general format is that of an 'INI' file with category section names in ; square brackets, followed by one or more name=value pairs in that ; category. Anything on a line after a semicolon is just a comment. ; ; Comments and blank lines are not allowed within a category, ; just in front of it. You know, since these are probably never going to ; be back-compatible with Arcadia SynSpace, I could really just improve ; the parser in this version. Someone should remind me to do that :-) ; ; Values are often comma-delmited strings with many individual field values ; in a single line. use double quotes, if required, in individual fields ; ;----------------- ; CATEGORY: CREDITS ; ; This first category is required and should do the best possible job of ; crediting everyone's work, without violating anyone's privacy. ; ; When crafting your own StarMap, you can declare as much info about yourself as ; you like, using whatever keywords you like, but the ; game engine will only use certain keywords (declared here) in the display. ; ; Also, please keep it PG-13. Not because The Man is holding you down, ; but because it's the decent way to treat strangers. ; ; The final tags and how they are each used, is somewhat still in flux, but... ; ; mapname The player-friendly name of your map, may include spaces ; maproot An id unique to you, the author, that defines what campaign this map is part of ; campaign's share player data (i.e. progress on one map can affect state on another ; if they are part of the same campaign) ; alphanumeric only, no spaces allowed ; mapdesc player-friendly description of your map. Is seen in the 'discovered by' section ; author What you, the author would llike to be called. Probably should accept longer strings for "Bill and Dave of SpaceX" authorships ; sernum in theory, the author sernum. But I don't use that (too weird typing in your own sernum anyway) ; version in theory, an author might want to track/control this. ; email traditinally, the email the author would like to be contacted at, if any ; info something so similar to mapdesc, that I have no idea which one will win in the coming UI wars ; tags another vague promise.. some sort of tag language that can be used to filter the server ; instance list, should it become long and unwieldy ; [credits] mapname = Alpha Debuggeri maproot = samsynAPI0 mapdesc = API Test/Demonstration/Tutorial map author = Samsyn sernum = 1 version = 1.001 email = dan@synthetic-reality.com info = You should be able to copy and paste the boilerplate into your map, then add your scenes and bots. tags = API ;--------------- ; CATEGORY: SPAWN ; ; New Ship Spawn Points ; ; Up to 8 human players may control drones (additional bot drones provided as needed for NPCs). ; ; NOTE: Players think of themselves as a 'color' and their index number is not displayed. However, ; each player DOES have an index number. I apologize in advance, but sometimes those indices are ; the range 1 - 8, and sometimes they are the range 0 - 7. You just have to read my mind and do ; the right thing. ; --- ; ; Each of the eight ships (1-8) can be assigned a spawn point. That means, when ship N ; enters the galaxy, it will always appear in a certain spot. ; ; This is probably only a good idea if that certain spot holds some protective features, since ; otherwise people will simply wait by the spawn point (or surround it with mines). ; ; If you don't call out a spawn point for a ship, then that ship will spawn at a random location ; (well, semi-random... around the edge of the galaxy, so you can keep your gravity objects ; near the center to minimize the chance of spawning inside a sun. ; ; Note, for TEAM-BASED maps, you probably want to put all the team-ships spawn points near each ; other (and perhaps near the team's home base thingy, which it ought to have). ; ; Format of an entry is: ; ; Ship# = x, y, heading ; ; use negative values for x and y to get a random location.. or just don't include the entry. ; ; The heading argument is optional, but should (if included) be in the range 0-359 (degrees from galactic North) ; ; This map doesn't want a spawn table... but I'll do one anyway, just for ship #1.. for now... ; This actually puts ship 1 at risk of spawning in a star, since -1 will randomly pick any value ; So, OMITTING the line is better, if you really want random startup. I should probably add a -2 ; to explicitly be random-avoiding-the-center. ; ; X Y HDG [spawn] 1= -1, -1, 0 ;----------------- ; CATEGORY: STARS ; ; adds STARS, Planets, etc. (Gravity Objects) ; ; Your map may have up to 16 gravity objects on it. But that would be INSANITY. ; ; It is recommended that they be kept near the center of the galaxy, since gravity does ; not wrap over the galaxy boundaries. ; ; However, as you will see, you have a lot of setup possibilities with each star ; ; There are many numbers associated with stars, so count carefully :-) ; ; Format of an entry is: ; ; StarID = style, x, y, mass, reach, temperature, radius, red, green, blue, imageId ; ; That's 11 parameters per star. ; ; * Style ; make this 0 to turn the star OFF, 1 to turn it ON. ; ; note: if you do not explicitly turn off star 0, the map will default to ; ; one star in the center of the galaxy. ; ; * x, y ; the location of the star. (negative values will NOT make random stars) ; ; * mass ; This controls the pull of its gravity. The star you are used to ; ; has a mass of 1000 ; ; * reach ; unlike real mass, gravity extends no further than this from the star ; ; The default star has a reach of 2048 ( 8 grid segments) ; ; * temp ; The temp of a star controls how much it hurts when you get close to it; ; ; A negative temperature will make a healing star. ; ; The default star has a temp of 30. ; ; * radius ; approximately the physical radius of the star. Please don't make huge stars ; ; because they will look goofy. The default star has a radius of 128 ; ; for an invisible star, use a radius of 0. ; ; * R,G,B ; The red,green,blue values control the color of the star. Each is a ; ; decimal number between 0 and 255. The android version is not paletted, so ; ; you can use any color you like. ; * imageId ; if this value is greater than 0, then a bitmap is drawn instead of a colored circle ; ; Image ids refer to assets baked into the game, over which you have no control. ; ; which means you could use weird images, in addition to the official planet images. ; ; ; ; currently ids 28 - 46 are various planet images. But all the art is in there ; ; (so hello, planet 'page 4 of users guide'!) ; ; if someone were to send me free art, I would not object to adding it to the game for all to share ; ; for now, that is the only way to include bitmaps of higher resolution than FACE assets. ; ; (though the dream remains to let you import art via url (which basically then all players ; ; would also do, when playing your map, with some appropriate degree of caching) But you would ; ; have to guarantee persistent urls. So, for now, when you see 'imageId' it always means a ; ; baked in piece of art. But someday some rule like "negative imageIds actually use an index ; ; into a map-provided url list which sources the individual images" ; ; but again, that's not today. ; ; style x y mass reach temp radius red grn blu img [stars] 0 = 1, 4096, 4096, 1000, 2048, 30, 128, 200, 200, 0, 28 ;1 = 1, 300, 300, -100, 2048, -30, 128, 200, 200, 200, 0 ; repulsive gravity, healing star, white, near fuel depot ;2 = 1, 4096, 3096, 1000, 2048, 30, 128, 100, 100, 50, 0 ;3 = 1, 3096, 3096, 1000, 2048, 30, 128, 100, 20, 150, 0 ;----------------- ; CATEGORY: COLORS ; ; You may define up to 64 colors (color indices 0-63) to be used with the barriers ; Some colors are hard-wired, but others you can set to any RGB value you like ; This also controls the colors of the NPC ships. ; ; 0 = anything you like, defaults to GOLD. Default barrier color. ; ; Ship colors 1-8 are hardwired ; ; RED 1 2 BLUE ; GOLD 3 4 GREEN ; YELLOW 5 6 PURPLE ; WHITE 7 8 'BLACK' (gray) ; ; 9 - 63, anything you like, but defaults to the FACE editor palette ; ; Colors are defined as three decimal numbers (0-255) in the order Red, Green, Blue. ; ; red grn blu [colors] 0 = 255, 204, 0 ; gold 9 = 204, 51, 104 ; reddish (west team) 10= 53, 103, 255 ; blueish (east team) ; ;----------------- ; CATEGORY: PAINS ; ; You may define up to 16 pain levels (pain indices 0-15) to be used with barriers. ; Each barrier can then be assigned to one of these pain levels (default to level 0) ; ; The ship is then damaged by this amount when it touches the barrier. Note that ; flying parallel and close to a barrier might inflict pain multiple times. ; ; It is advised to keep pain level 0 set to no damage. ; ; This pain is de-rated by the ship's shields. ; ; Note: Negative pain HEALS the ship! (and is NOT de-rated by shield) [pains] 0= 0 1= 100 2= 500 3=-1000 ;----------------- ; CATEGORY: HORIZONTAL ; ; horizontal barriers ; ; format is: id=left, right, top, color, xpar, pain, group ; ; id number should be from 0 to 99 (100 lines max) ; ; left (must be less than right) ; 0 (far left) to 8191 (far right) ; ; right ; 0 - 8191 ; ; top ('y' value of horizontal line) ; 0 (bottom-most) - 8191 (top-most) ; ; color (optional): ; 0 color table index 0 ; 1 color table index 1 ; ... ; 63 color table index 63 ; ; xpar (optional) ; 0 bouncy wall ; 1 wall that ship 1 can go right through (private door) ; 2 wall that ship 2 can go right through (private door) ; ... ; 8 wall that ship 8 can go right through (private door) ; 9 wall that WEST team can go through (ships 1, 3, 5, and 7) ; 10 wall that EAST team can go through (ships 2, 4, 6, and 8) ; 11 wall that ALL SHIPS can go through ; 12 wall that NW can go through ; 13 wall that NE can go through ; 14 wall that SW can go through ; 15 wall that SE can go through ; ; Then, sorry.. ADD ONE HUNDRED if bullets can go through it. ; ; So, 111 = transparent to all ships and bullets. More of an open window than a wall ; ; pain (optional) ; 0 pain table index 0 (usually you should set that to 0 pain) ; 1 pain table index 1 ; ... ; 15 pain table index 15 ; ; group (optional, assumed 0 - no group) ; individual barrier line segments (horz and vert) can be optionally ; bound to a group Id. The script can then treat all the barriers of ; a group as a unit. Basically so you can create a 'door' out of one ; or more barriers, then the script can enable/disable the group as ; needed to open/close the 'door'. ; ; You might prefer to leave this section mostly blank and then let ; your script create the barriers it needs programmatically, but ; this table is what can be edited in-game, visually (someday) ; ; On this map, we declare two FLAG zones(4/5 abd 6/7), ; and a 'garage' (0/1/2/3) where you can recharge. Note that ; there are also VERTICAL barriers for those same zones ; ; left, right, top, color, xpar, pain, group [horizontal] 0 = 19, 256, 20 1 = 19, 100, 255 2 = 100, 155, 255, 6, 111 3 = 155, 256, 255 4 = 1948, 2148, 1948, 9, 11, 0 5 = 1948, 2148, 2148, 9, 11, 0 6 = 6044, 6244, 6044, 10, 11, 0 7 = 6044, 6244, 6244, 10, 11, 0 ;----------------- ; CATEGORY: VERTICAL ; ; vertical barriers ; ; format is: id=bottom, top, left, color, xpar, pain, group ; ; Arguments are same as for horizontal barriers, except you provide the bottom and ; top of a vertical line segment, at 'x' position 'left' ; ; bottom must be less than top (increasing y is up the screen) ; ; bottom, top, left, color, xpar, pain, group [vertical] 0 = 19, 256, 20 1 = 19, 256, 255 2 = 100, 125, 45, 2, 0, 3 3 = 100, 125, 225, 1, 0, 2 4 = 1948, 2148, 1948, 9, 11, 0 5 = 1948, 2148, 2148, 9, 11, 0 6 = 6044, 6244, 6044, 10, 11, 0 7 = 6044, 6244, 6244, 10, 11, 0 ;----------------- ; CATEGORY: POWERUPS ; ; Powerups are objects which appear at specific or random locations at specified time intervals ; and which can be 'picked up' by passing ships. This table lets you 'schedule' which powerups ; are available on your map, where they spawn, and how long until they respawn. ; ; Think of this list as the source of the 'pyramids' themselves and their scheduled ; appearance times and locations. This is NOT where you define new kinds of weapons ; and such. This is just how you schedule their periodic appearance for pickup. ; ; Format is: id= style, x, y, seconds ; ; ID ; 0-255 (each pup occupies one 'slot' in a 256 entry table) ; ; Style (what sort of pup it is. You might have the same style of pup in several slots) ; 0 No such pup, only use this if you are too lazy to delete table entries ; 1 RESERVED ; 2 Pack of Homing Missiles ; 3 Pack of Plasma Mines ; 8 Ship WEAPON upgrade ; 9 Ship SHIELD upgrade ; 10 Ship ENGINE upgrade ; 11 100% Energy Restore ; 12 20% Energy Restore ; 13 Concealed Trap ; 14 Warp Coil ; 15 Plasma Shield ; 22 Encrypted Starmap ; ... reserved for future stock pups ; 100 android: start of map-defined pup Ids ; 199 android: absolute final map-defined value ; when in no teams mode ; 240 Team 0 Flag (no ships belong to this team) ; when in 8 team mode ; 241 Ship 1 Flag (usually only WEST and EAST flags should be used) ; ... ; 248 Ship 8 flag (only ship 8 belongs) ; ; when in 2 team mode: ; 249 WEST Team Flag (ships 1, 3, 5, and 7 belong) ; 250 EAST Team Flag (ships 2, 4, 6, and 8 belong) ; ; 251 ALL Team flag (all ships belong to this team, for collaborative maps) ; ; when in 4 team mode: ; 252 NW Team Flag (ships 1, 3) ; 253 NE Team Flag (ships 2, 4) ; 254 SW Team Flag (ships 5, 7) ; 255 SE Team Flag (ships 6, 8) ; ; (missing numbers represent things which are not yet implemented, but reserved for future development) ; ; X X-Location of powerup on map ; -1 Pick a random location ; 0-8160 Specific location (rounded to closest 1/256th of Galaxy) ; so valid values are 0, 32, 64, 96, ... 8128, 8160. ; any other values will be 'rounded down' to closest multiple of 32. ; ; Y Y-Location of powerup on map (same units as X) ; ; secs How many seconds elapse after the powerup is picked up, before a new one spawns in that slot ; 0-N ; ; Note: The first 20 slots or so are automatically filled for you with random powerups. So if you ; Don't override the first 20 slots, that is what you will get. Sort of like the description for ; STARS. If you *do* override those slots, then your map takes precedence over the defaults. ; ; on THIS map, we accept the default pups from slots 0 to 20, and add: ; ; 22 is encrypted map drop ; 249 WEST team flag, ; 250 EAST team flag ; ; pupId, X, Y, seconds [powerups] 22 = 22, -1, -1, 600 249 = 249, 2048, 2048, 600 250 = 250, 6144, 6144, 600 ;----------------- ; CATEGORY: ZONES ; ; Zones are rectangular regions with special properties applying to ships and bullets which ; pass in and out of them. ; ; Since the rectangles might overlap, this list is processed in reverse order and the ; first 'hit' controls the behaviour of that point. So the first entry on the list is the ; last to be considered and should be the 'largest' zone. If your zones do not overlap, then ; ignore this paragraph. But you might want the first zone in the list to do something like ; span the entire galaxy so you can set some 'global' behaviour (no bullets, for example) which ; is then overridden while in smaller zones defined later in the list. ; ; Just to keep things happy, number your zone IDs 0-N from top to bottom. Belt and suspenders that ; way. ; ; Format is: id= style, left, top, right, bottom, texture, team, pain, bullets, wayPtID, friction, cx, cy, group ; ; ID (0-99) Although you may define up to 100 zones, your map will be faster if you define fewer. ; 0-99 ; ; Style (what sort of zone it is, if it has any super special purpose) ; 0 Disabled Zone ; 1 Normal Zone, no special meaning (other than properties) ; 2 GOAL Zone (team set by 'team' property) (earn points by dropping FLAGs here) ; 3 WayPoint Zone (wayPtID property sets additional info) ; ; left, top, right, bottom (Coordinates of sides of zone rectangle) ; 0-8191 ; ; Texture (reserved) ; 0 ; ; Team (used to interpret pain and CTF HOME) ; 0 belongs to no one ; 1-8 Specific Ships 1-8 ; 9 WEST Team (valid for CTF HOME) ; 10 EAST Team (Valid for CTF HOME) ; 11 All Ships ; 12 NW Team Flag (ships 1, 3) ; 13 NE Team Flag (ships 2, 4) ; 14 SW Team Flag (ships 5, 7) ; 15 SE Team Flag (ships 6, 8) ; ; Pain (Applies only to ships of specified team) ; 0 No pain/heal ; >0 Hurts all ships EXCEPT those matching team property ; <0 Heals ONLY those ships which match team property ; Units are sweeten to taste, but 10 drains you pretty fast, and -100 charges you quickly ; ; Bullets (What happens to weapons inside this region) ; 0 No special effect ; 1 Ship trigger is disabled, but bullets can live ; 2 Ship trigger is OK, but bullets expire ; 3 Trigger and bullets are disabled. ; ; WayPtID (Valid for waypoint style only) ; 0 START position (stopwatch is cleared while in here, starts running when you exit) ; 1 FIRST Waypoint (waypoints must be crossed in order, stopwatch split time kept per waypt ; 2.. Additional Waypoints as needed ; -5 A negative waypoint means the END of the race and the final stopwatch is shown. ; So, a complete race would number its waypoint zones: 0, 1, 2, 3, 4, ..., 12, 13, -14 ; (You can have as many waypoint zones as fit) ; ; Friction (Volte6's cool idea) ; 0 Normal space ; 1-100 you coast to a stop if you don't apply thrust. At 100 you stop almost at once ; <0 Undefined, but I will try to make it boost your speed or do something 'interesting' ; ; cx, cy (river current) ; 0 Normal Space ; +/-N Adds this velocity component to your normal motion, to create a sort of conveyor belt/river ; Effect (hard to go upstream, for example) Good for that "pulling people towards danger" ; effect. A value of 50 is a slow current, 500 is medium. Sweeten to taste. ; ; group (assumed 0, no group) ; if you assign a zone to a group, then the script can refer to all zones in that group ; as a collection. Not sure if that means anything yet, but that's the plan ; id= style, left, top, right, bottom, texture, team, pain, bullets, wayPtID, friction, cx, cy, group ; ; For some reason, I seem to need a picture to work this out. ; ; * for a 2 team map, WEST (4 players on red side) ; vs EAST (4 players on blue side) ; ; ; 6k ; [EAST] zone 10 ; Team 10 ; Flag 250 ; ; 4k ; o ; STAR ; ; 2 k ; [WEST] zone 9 ; Team 9 ; Flag 249 ; ; [garage] ; ; ; Grab the enemy flag from THEIR zone, and drop it back in your OWN zone for a goal ; ; Format is: id= style, left, top, right, bottom, texture, team, pain, bullets, wayPtID, friction, cx, cy, group ; ; zones must be in priority order, larger earlier in list ; zone 1 a large zone around star ; zone 9 home/goal zone for WEST team, and home of WEST flag - 249 ; zone 10 home/goal zone for EAST team, and home of EAST flag - 250 ; ; ; st left top rigt btm tex team pain bul wp fr cx cy gp [zones] 1 = 2, 3000,3000,7000,7000, 0, 0, 0, 0, 0, 0, 0, 0 9 = 2, 1948,1948,2148,2148, 0, 9, 20, 3, 0, 0, 0, 0 10 = 2, 6044,6044,6244,6244, 0, 10, 20, 3, 0, 0, 0, 0 ;----------------- ; CATEGORY: PROPS ; (UNDER DEVELOPMENT) ; ; Basically this is a list of name-value pairs that control elements of ; the game engine. You cannot add props willy nilly, as we accept only ; stock property names ; ; --------- ; Property: NumTeams ; ; 0 - no teams (every man for himself) ; 1 - fully human coop (can't hurt other humans) ; 2 - classic Left vs Right ; 4 - NW vx NE vs SW vs SE ; 8 - every man for himself, but with flags possible ; ; In general, team-mates are treated the same as you l ; Thumbs(ships 0-7) are assigned to teams (0-3) like this ; ; [0:RED | BLUE:1] ; [2:GOLD 12 | 13 GREEN:3] ;----------------+------------------ <-- (numTeams=4) adds this split ; [4:YELLO 14 | 15 PURPLE:5] ; [6:WHITE | BLACK:7] ; ; Team IDs (must be interpreted in context of numTeams) ; ; all modes ; 0 'belongs to no team' ; 8 team mode ; 1-8 'belongs to team: player N-1' ; 2 team mode ; 9 West Team ; 10 East Team ; 1 team mode ; 11 Cooperative (all player ships) ; 4 team mode ; 12 NW Team (ships 0, 2) ; 13 NE Team (ships 1, 3) ; 14 SW Team (ships 4, 6) ; 15 SE Team (ships 5, 7) ; ; --------- ; Property: SafeWeapons ; ; Can your own bullets hurt you? ; ; Value ; ; 0 ; your weapons are only safe for a moment after you fire. ; 1 ; your weapons will never hurt you or your team mates ; ; -------- ; Property: ShowHandbook ; ; Does the player see the built in handbook when starmap is loaded? ; ; Value ; 0 ; No, my script is going to show a cool cut scene and the handbook would be in the way ; 1 ; Yes, I have nothing dynamic to show, let them see their precious handbook. ; [props] NumTeams = 2 ; for individual tutorials, but team aspects exist SafeWeapons = 0 ShowHandbook = 0 ;------------------------------------------------------------------------------ ; START OF BOTTOM HALF OF STARMAP FILE ; this last category declares the end of this starmap, as everything past this tag is lua syntax. ; this category is completely optional ; ;]]-- In theory, the entire top half is inside a lua block comment [SCRIPT] -- Remember, lua uses 2 dashes for comments -- INCLUDE API_1 (just a comment, but the API is pre-pended here) ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- -- In theory, the standard infrastructure is all ABOVE this point and -- can be largely ignored by you, the map author. Here you get the -- benefit of that work, and can focus on crafting individual lua -- tables that tell your story in scenes and bots. -- REMEMBER YOUR COMMAs. When adding lines to a table, remember the commas. --================================================================================= -------------------- -- My own globals, if any -------------------- -- these match ids I used in the top half of the map glZoneIdStar = 10 -- this zone is around star, for detecting approach glKlausX = 100 glKlausY = 8000 glHomeX = 0 -- we will compute these later glHomeY = 0 glEnemyHomeX = 0 glEnemyHomeY = 0 -- beacons I will use beaconIdTutor = 0 beaconIdTarget = 1 beaconIdMe = 2 beaconIdKlaus = 3 beaconIdSlack = 4 -- pupIDs (not the same as pupIndices, use the range 100-199) PupIdMiner = 100 -- an escaped miner, disguised as a powerup -------------------- -- BOTS IN THIS STARMAP -------------------- -- Here we declare all the 'characters' we need on this starmap -- and configure each of them in all detail. You will find inheritance -- useful when making more than one of the same style bot. -- baked-in face assets: faceDataWATTE = "DATA: ____0RRRPRR0______0TTSLLLLLTT00_00bLLLLLSLSLLb000LSLSSVUVVSSLSL00LLSV00UU00USLL00LLVUUUVVUTTTLL00LSVU79UU79TTSL00LVVU99UT99TTSL00LSUUUUTTTTTSSL00LSUUUTVSTSSSRL0_0UUUTTTTT0SRR00_00UTR0000RSR00__00UTSTTTRRRR00___00TTTTRRRR00_____000TRRR000_______0000000_____" -- Our nemesis, should be tough botSlack = newBot( botRoot, { id = "boss", -- must be unique among OBJECTs on this map (bots and scenes share this namespace) ship = 9, -- gets a thumb and a pilotInfo spawnX = 1200, -- gets a hard coded spawn location (does -1 here work for a random location?) spawnY = 2200, spawnZ = 0, -- is in the plane of the system brain = nil, -- spawnBot will set this to the coroutine ref when thinking remarkWhenDead = 1, -- has the opportunity to spout some last words radarColor= 8, -- if you have a thumb, I think that will take precedence, this is for -- things without thumbs that need to appear on the radar (enemy clone factory) -- if you have a thumb (first 12 NPCs get thumbs, but the first 8 are intended for human players) -- then you need this info for your PilotInfo panel to be interesting when people tap your thumb pilotInfo = { rank = "Dread Pirate", name = "SLACK", -- use obj.name() and not this directly face = "FACE_025", -- stock FACE asset to use shell = "SHIP_007", -- stock SHIP asset to use podW = 1, -- starting pods podS = 14, podE = 14, rating = 1000, -- starting rating won = 0, -- starting stats lost = 0, lang = 2, -- G = 0 ? declared language rating of bot. }, } ) --dumpTable( botSlack, "SLACK BEFORE MERGE " ) -- Now, how simple can the SECOND bot be? -- only include fields that need to differ from parent botKlaus = newBot( botSlack, { id = "klaus", -- must be unique among OBJECTs on this map (bots and scenes share this namespace) ship = 10, -- gets a thumb and looks like a player spawnX = 1200, -- gets a hard coded spawn location (does -1 here work for a random location?) spawnY = 2200, spawnZ = 0, brain = nil, -- spawnBot will set this to the coroutine ref when thinking radarColor= 8, -- if you have a thumb, I think that will take precedence, this is for -- things without thumbs that need to appear on the radar (enemy clone factory) -- if you have a thumb (first 12 NPCs get thumbs, but the first 8 are intended for human players) -- then you need this info for your PilotInfo panel to be interesting when people tap your thumb pilotInfo = { rank = "Miner", -- not actually used yet name = "KLAUS", -- use obj.name() and not this directly face = "FACE_014", -- stock FACE asset to use shell = "SHIP_004", -- stock SHIP asset to use podW = 4, -- starting pods podS = 4, podE = 4, rating = 1000, -- starting rating won = 0, -- starting stats lost = 0, lang = 2, -- G = 0 ? declared language rating of bot. }, } ) botMinion1 = newBot( botSlack, { id = "minion1", -- must be unique among OBJECTs on this map ship = 11, -- gets a thumb and looks like a player spawnX = 3000, -- spawns in formation around SLACK spawnY = 3000, spawnZ = 0, brain = nil, pilotInfo = { rank = "First Mate", name = "SMITTY", face = "FACE_026", shell = "SHIP_004", podW = 0, -- nerf these guys podS = 7, podE = 2, -- rating = 1000, -- won = 0, -- lost = 0, -- lang = 2, -- G = 0 ? }, } ) --dumpTable ( botSlack, "SLACK AFTER MINION1 " ) botMinion2 = newBot( botMinion1, { id = "minion2", -- must be unique among OBJECTS on this map ship = 12, -- gets a thumb and looks like a player spawnX = 3100, spawnY = 3100, brain = nil, pilotInfo = { rank = "Minion", name = "JARJAR", face = "FACE_006", shell = "SHIP_001", }, } ) --- -- During the tutorial, let's have a friendly bot botTutor = newBot( botMinion1, { id = "tutor_1", -- must be unique among OBJECTS on this map ship = 8, -- gets a thumb and looks like a player spawnX = 1000, spawnY = 2200, brain = nil, pilotInfo = { rank = "Lieutenant", name = "WATTE", face = faceDataWATTE, shell = "SHIP_006", }, remarkWhenDead = 1, ai = function( bot ) log( 1, 'bot ' .. bot.id .. 'started ai coroutine' ) -- as a tutor, I mainly just watch end, } ) ---- -- also we need a target drone. Note that we only see one of these, our -- own (and everyone uses the same index) botTarget = newBot( botRoot, { id = "tgt_1", -- must be unique among OBJECTS on this map ship = 20, -- no thumb spawnX = 1000, spawnY = 2200, brain = nil, remarkWhenDead = 1, pilotInfo = { rank = "Target", name = "CHEETA", face = "FACE_009", shell = "SHIP_008", podW = 2, podS = 7, podE = 2, }, ai = function( bot ) log( 1, 'bot ' .. bot.id .. 'started ai coroutine' ) -- as a target, I won't do much end, } ) -- log until we're sure merge is reliable log( 1, "------BOSS BOT --------" ) dumpTable( botSlack, "Slack ") log( 1, "------MINION1 BOT --------" ) dumpTable( botMinion1, "Minion1 " ) log( 1, "------MINION2 BOT --------" ) dumpTable( botMinion2, "Minion2 " ) log( 1, "------TUTOR_1 BOT --------" ) dumpTable( botTutor, "Tutor1 " ) log( 1, "------TARGET_1 BOT --------" ) dumpTable( botTarget, "Target1 " ) -- all the bots on this map. (bot tables must have been declared ahead of this) -- only bots in this list will receive messages or have their coroutines stepped. --botList = { botSlack, botKlaus, botMinion1, botMinion2, botTutor, botTarget } ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- -------------------- -- SCENES IN THIS STARMAP -------------------- -- Each starmap should probably have a single scene, I am calling it Main for -- now, which is responsible for greeting the player and explaining the overall -- point of the map. -- For example, only the Main scene would respond to the onAWARE and onLAUNCH -- messages by playing cutscenes, while any scene might still want to be -- notified when those events take place. -- Again, this is all up to you. The only thing you are completely stuck with -- is onMsg() :-) You're free! Explore and create! Be creative and nice! --================================================================================= -- This map has several tutorial scenes on it, and one MAIN scene that -- coordinates things. -- -- the tutorials are not distributed, so each player gets their own copy, even -- though they are physically in the same place as other players, who see their -- copies, etc. Potentially confusing -- I do one tutorial after another, and increment this so I know which one I am doing -- note this is GLOBAL TO THE MAP. Each tutorial sets it upon completion, and -- starts the next tutorial, by sending a uMessage to a cleverly designed -- object id "tutorial_N" tutorialState = 0 nextTipOfTheDayIndex = 0 tips = { "Our struggle has been long, with few victories seen. Morale is low.", "Our pleas have gone unheeded for so long, we despair that no one hears our calls.", "Or perhaps they DO hear us. Darvon says they ignore us on purpose! ", "But Darvon is an idiot. He always overloads his drone with weapon pods, so he is slow and easy to destroy!", "You DO plan to help us, don't you?", "Because there are lots of other drone runners out there, you know. You're not the... Only!", "But we hear you are the BEST, so please don't let us down! We're really counting on you!", "Hello?", "... ...", "Darvon! Are you sure this thing is on? What do you mean its out of IMPROOVIUM! That's both impossible and ironic!", "If anyone is out there... if anyone can hear us... please join us. please pick a drone thumb color and join us.. before it is too late!", } function tipOfTheDay() numTips = #tips if( nextTipOfTheDayIndex >= numTips ) then nextTipOfTheDayIndex = 0 -- but we inc it before we use it end nextTipOfTheDayIndex = nextTipOfTheDayIndex + 1 return tips[ nextTipOfTheDayIndex ] end --------- -- MAIN SCENE --------- -- This scene greets the player, and introduces them to the challenge, -- which will take place over a handful of scenes that teach the player -- the basics of piloting and fighting, while conquering a boss and -- his minions, and setting free the imprisoned miners. -- And yes, there are coded secrets behind all the names and keywords used -- in this map :-) DALVIK being, of course, the java virtual machine -- used by android at this time. Other names are more self-referential, -- and I apologize for anything too cloying. -- onAware plays a cut scene before ship is launched -- "Here's the problem, will you help?" -- (launching is your acknowledgement) -- can then tutorialiate with tips until you launch and extoll you -- onLaunch plays a cut scene after ship is launched -- "Welcome recruit, ;et's get you checked out first" -- starts tutorial scene, which walks me through stuff in a solo -- experience not shared by other players, though they will see me do it. -- onDead maybe something, but only after a long delay to let -- all the other dead-related messaging scroll by -- I guess this scene is always hosted by the moderator, and its primary states -- are -- 0 idle, freshly loaded map where nothing has happened yet --------------------------------- ---- ---- MAIN SCENE ---- --------------------------------- -- our possible states STATE_IDLE = 0 STATE_LEARN_TRIGGER = 1 ------------ -- TOKENS USED ON THIS MAP TokenFinishedTutorial = 1 -- let's you skip straight to the open Pen TokenFoundPals = 2 -- let's you skip finding his friends TokenDisabledReactor = 3 -- let's you skip the reactor capture the flag TokenUnmaskedSlack = 4 -- let's you skip the Slack boss fight sceneMain = newScene( sceneRoot, { id = "Main", -- each OBJECT in map needs a unique tag to receive peer messages state = STATE_IDLE, -- tutorial data hasShotHimself = 0, -- count them since the map was loaded hasKilledHimself = 0, -- the root scene will play some cutscenes automatically, in response -- to certain game engine messages (onAWARE and onLAUNCH for example). -- So scenes like this one, can just override those cutscenes. ----------- -- onAWARE: i start this when they can first see it, before they pick color -- but after they connect to server instance onAwareCutScene = function( scene ) scene.state = STATE_IDLE -- back to the beginning wait( 3 ) -- they see map preview setCamVeil( 0, 500 ) -- fade map preview to some percent of normal wait( 1 ) -- let the preview fully disappear -- this initiates a full zoom in from galaxy view. I should probably fade that in as it is pretty abrupt setCamStar( 0 ) -- move camera to watch central star (planet DALVIK in this case) setCamZoom( CamZoomGroup, 2000 ) -- start zoomed in on the planet setCamOrbit( 5 ) -- gently orbit wait(3) -- Let there be Title announce('SLAVES OF IMPROOVIUM.' ) wait( 5 ) -- scroll the following text in fancy narration mode textStory( myStoryStyle, [[ / / Planet DALVIK drifts alone through this star system... arid, foul and dusty. But DALVIK's cold heart hides a secret: the planet's core is almost solid IMPROOVIUM, an element treasured throughout the galaxy! / / This has attracted the attention of villainous scum like Space Pirate SLACK, Nemesis of Free Space! / / SLACK has hijacked several drone factories which even now churn out robot minions to do his dark will. / / Even worse... he has enslaved thousands to work deep in the planet's IMPROOVIUM mines, where life is dark, brutal and short. / / We seek new DRONE RUNNERS, willing to help us free the slaves, vanquish the pirates and destroy their evil drone factories! / / / Will you help us? ]] ) wait( 7 ) announce( "Are you the... One, " .. myShipName .. "?" ) wait (20) -- let it scroll off screen --setCamVeil( 100, 500 ) -- turn the veil off -- try to be informative AND entertaining while they puzzle which thumb to tap -- right now, this will be aborted as soon as another cut scene starts, even one in -- another scene, and I think that is probably wrong. scenes probably also want -- persistent brain coroutines... but still, there is the concept of the singleton -- cut scene that puts up text and changes camera and such. while 1==1 do wait(15 + (15 * math.random()) ) -- between 15 and 30 beats, is the idea --setCamVeil( 25, 500 ) -- turn the veil on textStory( 0, tipOfTheDay() ) wait( 3 ) --setCamVeil( 100, 500 ) -- turn the veil off end end, ------------ -- onLAUNCH: this is the main scene, so it introduces our tutor and then -- hands us off to a series of task coroutines until I complete everything -- it remembers several tokens so I can skip completed sections on -- later visits. It also offers an OPTION command to help you forget onLaunchCutScene = function( scene ) log(1," Starting onLaunch cut scene") -- Again, the player just launched, so we just learned their ship index, -- so we have some math to do we could not do before. -- this is not currently used scene.state = STATE_LEARN_TRIGGER -- we introduce the trainer and the training pen scene.hasShotHimself = 0 -- fresh tutorial start -- now that we know our shipindex, so we can put SLACK on the opposing home zone if ( (myShipIndex % 2) == 0) then -- I am Team West glHomeX = 1948 + 50 glHomeY = 1948 + 50 glEnemyHomeX = 6044 + 50 glEnemyHomeY = 6044 + 50 botTutor.team = TeamWest botTarget.team = TeamWest botKlaus.team = TeamWest botSlack.team = TeamEast botMinion1.team = TeamEast botMinion2.team = TeamEast else -- I am TeamEast glHomeX = 6044 + 50 glHomeY = 6044 + 50 glEnemyHomeX = 1948 + 50 glEnemyHomeY = 1948 + 50 botTutor.team = TeamEast botTarget.team = TeamEast botKlaus.team = TeamEast botSlack.team = TeamWest botMinion1.team = TeamWest botMinion2.team = TeamWest end -- This is a tutorial, disable all controls and then introduce them -- one by one. We shove them inside their own personal training pen -- to ensure each player has an individual experience on a shared -- server forceShipToStart( myShipIndex ) -- Freeze the newbie in place, with broken controls -- create our Tutor right away, since the camera will start on his drone, not ours -- this also means the first drone the player sees is a cool one, and then a little -- joke when they get the one they are assigned as a newbie -- We spawn WATTE again, but this time he is zooming in from parts unknown -- possibly, that makes no sense, since he was just here a second ago -- I just wanted to test the 'from' arguments -- I only include the messages because I want him to stop when he gets there -- (and the presence of a message also enables that behaviour) -- this is surprisingly effective, zooming in on a speeding ship, while -- having the camera orbit it, slowing as we reach our destination. -- I wish I had some good music for this botTutor:enter( 0, -1, botTutor.spawnX, botTutor.spawnY, 4000, 4000, "HitMark", "LeftMark" ) -- behaveMask, tgtIx, toX, toY, fromX, fromY, uMsg, uMsg2 setCamSpy( botTutor.ship ) -- move camera to watch tutor setCamZoom( CamZoomCloseUp, 2000 ) -- start zoomed in on his ship setCamOrbit( 5 ) -- gently orbit his ship wait(10) -- they just entered and probably got slammed with notifications, let those drain -- This is just a link from the onAWARE scrpt, a final line of narration from the mystery voice announce( "Good. Welcome to the fight, " .. myShipName, "!" ) wait(3) -- close the gate, maybe they get a glimpse setMapBarrierStateV( ixDoorBarrierV, 3 ) -- animate to closed -- But now we get an incoming radio message (popup dialog) -- the radio is from our tutor, and he will set us our first task -- INTRODUCTION -- the colon here causes it to add the object as a hidden self pointer -- plus it looks a little like a screenplay, but don't be confused! -- Note that the script only pauses when you wait, When you 'say' or -- 'ask' something, it moves right along. Such messages are even -- queued for the reader, so you must periodically pause for that -- queue to be drained, or you will lose the interactivity. -- The only difference between 'say' and 'ask' is that a 'say' window -- will close automatically after the user has had time to read it, -- but a 'ask' window will remain open until the player clicks on its -- green arrow. Mission critical data should be delivered with 'ask' -- to be sure the user sees it. -- leave this set to 0 normally. Otherwise it will ONLY do the numbered section -- you specify, and will skip all the rest as if the player had the token -- set this to 0 for normal operation scene.overrideToken = 0 -- allows me to skip chunks in development if ( scene.overrideToken ~= 0 ) then botTutor:ask( "You realize, of course, you are forcing task " .. scene.overrideToken .. ". Don't ship like this!" ) end -------- -- TASK 1: handoff from launch, and learn controls log(1," Testing if they have done Task 1 ") if( (scene.overrideToken == 1) or not hasToken( TokenFinishedTutorial ) ) then -- you have not yet learned the controls scene:tut_LearnTheControls() setToken( TokenFinishedTutorial, 1 ) else -- you have learned the controls, but are still stuck in a pen -- until I release you. This leaves you with an open pen and a realiable -- source of full restore PUPs and a safe place. scene:tut_UnlockThePen() end -- in all paths, the pen must be open by now --------- -- TASK 2: Meet KLAUS and rescue his pals log(1," Testing if they have done Task 2 ") if( (scene.overrideToken == 2) or not hasToken( TokenFoundPals ) ) then scene:tut_HelpKlausFindPals() setToken( TokenFoundPals, 1 ) end -------- -- TASK 3: Disable the core of the enemy clone reactor log(1," Testing if they have done Task 3 ") if( (scene.overrideToken == 3) or not hasToken( TokenDisabledReactor ) ) then scene:tut_DisableCloneReactor() setToken( TokenDisabledReactor, 1 ) end ---------- -- TASK 4: unmask SLACK log(1," Testing if they have done Task 4 ") if( (scene.overrideToken == 4) or not hasToken( TokenUnmaskedSlack ) ) then scene:tut_UnmaskSlack() setToken( TokenUnmaskedSlack, 1 ) end ---------- -- REPLAY VALUE -- At this point, they have fully experienced the map's content, and we need -- to congratulate them log(1," Completed all programmed content") botTutor:say( "But you've proven yourself, " .. myShipName .. ", you really have what it takes!" ) botTutor:say( "You're HERO material! Nothing less than that!" ) botTutor:say( "Now get out there and defend our Galaxy!" ) -- Ideally we would now have random rare spawns pop up so there was a multiplayer -- reason to hang on this map wait( 10 ) -- switch to a hint of the day mode here botTutor:say( "You can melee here, or maybe use the STARMAP button to explore new star systems." ) botTutor:say( "You can use the OPTIONS/STARMAP menu to reset this map, to do it again." ) botTutor:say( "Or you can just search here for more encrypted starmaps." ) end, -------- -- Standard intro that leaves you in an open training pen, with a powerup recharge tut_UnlockThePen = function( scene ) log(1," Starting Unlock the Pen") -- acknowledge botTutor:ask( "Welcome back, " .. myShipName .. "! Ready for me to unlock the gate?" ) waitOnDlg( 0 ) -- camera to 'me' setCamOrbit( 0 ) -- stop orbiting wait(1) setCamZoom( CamZoomWide, 200 ) -- zoom out to normal scale wait(2) setCamSpy( myShipIndex ) -- repair all makeRepairs( myShipIndex, REPAIR_MASK_ALL ) -- zero defects! -- free gifts? -- recurring heal pup in pen -- does this work for non-moderator? resetPupXY( myPupIndexHeal, 11, myPenPupX, myPenPupY, 1, 20 ) -- remove pen wall wait( 3 ) -- let camera stabilize botTutor:say( "Great! Let's get you out of there!" ) wait( 2 ) setMapBarrierStateV( ixDoorBarrierV, 2 ) playSound( SOUND_BARRIER_CHANGE ) waitOnDlg(3) -- let them appreciate the wall disappearing -- give a hint/reminder, but this ends the cutscene botTutor:say( "Your drone is fully repaired, but you'll want more equipment!" ) botTutor:say( "Just avoid gravity long enough, and maybe you'll find a useful powerup!" ) waitOnDlg( 2 ) botTutor:exit( 4000, 4000 ) -- head off in the direction of DALVIK botSlack:exit() -- mainly so he isn't still there after we resurrect log(1," Finished: Unlock the Pen") end, -------------------------------------------------------------- -- Storyline (repeats on every visit) -- -- Locales -- * WEST and EAST each have a clone factory (with flags) -- SLACK appears to belong to the Player's opposing team (EAST vs WEST) -- Player's home factory is a safe place for the player, and WATTEs retreat -- Enemy home factory zone drains energy from player -- Enemy zone is SLACK's retreat, guarded by SLACK's minions -- * Behind the energy depot (Klaus Spawn) -- * slack-in-base location -- * wherever the slace/watte battle ends (watte death scene) -- -- -- Gameplay -- * There are escaped miner powerups which stack on your pup bar. -- click pup to drop off for points/rewards -- * at some point, minions begin guarding the enemy clone reactor -- 'flags' are actually 'clone reactor core crystals' and if you steal 3, you destroy the core -- * which makes it harder than usual to take enemy flag from center of factory -- * but if you do get the enemy flag back to your own base, it damages the enemy base 1 point -- * your goal is to destroy the enemy base, which exposes SLACK to you -- * you then have a final battle with SLACK to complete the map -- * you beat him, and he turns out to be WATTE in disguise -- * it was all a test, you're ready to move on -- but you can replay the map as much as you like ----------------------------------------------------------------- -- TUTORIAL: Learn the Ship Controls tut_LearnTheControls = function( scene ) log(1," Starting: Learn the Controls") setCamZoom( CamZoomCloseUp, 2000 ) botTutor:ask( "Can you hear me, " .. myShipName .. "?" ) -- does not auto-close -- I need the player to really be there, so will hold here until they close the dialog waitOnDlg( 1 ) -- wait for all pending messages to clear botTutor:say( "Great! I think your camera is on me! I'll turn my beacon on..." ) wait( 5 ) setBeacon( beaconIdTutor, botTutor.ship, 1 ) waitOnDlg(1) botTutor:say( "I'm just outside this bullet-proof training pen." ) waitOnDlg(1) botTutor:say( "YOUR drone is INSIDE the training pen, being repaired, all systems down." ) setCamZoom( CamZoomWide, 1000 ) -- pull back to show pen and both ships wait( 2 ) setCamSpy( myShipIndex ) -- recenter on player waitOnDlg(1) wait( 5 ) -- let them admire their ship inside the pen -- make my beacon flicker and go out botTutor:say( "Let's turn YOUR beacon on." ) wait( 1 ) setBeacon( beaconIdMe, myShipIndex, 1 ) -- put a beacon on myself wait( 6 ) -- this is also letting the camera catch up playSound( SOUND_INSUFF_CHARGE ) -- electric buzz setBeacon( beaconIdMe, -1, 1 ) -- turn off my own beacon wait( 3 ) setBeacon( beaconIdMe, myShipIndex, 1 ) -- turn on my own beacon wait( 2 ) playSound( SOUND_INSUFF_CHARGE ) -- electric buzz setBeacon( beaconIdMe, -1, 1 ) -- turn off my own beacon wait( 3 ) botTutor:say( "Well, we'll fix your beacon later, I guess. For now, you're the guy without a beacon." ) waitOnDlg(1) -- TRIGGER TEST (where we force the recruit to shoot themselves) -- Seems to work best if the repaired item appears first, then the announcement of repair botTutor:say( "I'm OUTSIDE the pen, in case you get trigger happy..." ) botTutor:say( "or, in case you get a trigger... we're still working on that!" ) waitOnDlg(4) setCamOrbit( 0 ) -- stop orbiting (and end up directly behind ship) makeRepairs( myShipIndex, REPAIR_MASK_TRIGGER ) -- trigger appears hiliteUI( HL_TRIGGER ) -- make it glow for a few seconds wait( 2 ) -- admire it a moment before dialog pops right on top of it (on narrow screens) botTutor:say( "Hold on... ok.. you've got trigger! It's that circle on the left." ) waitOnDlg(2) -- wait a moment so they can actually SEE it, if covered -- wait forever until he obeys, causing him to shoot himself in reflected fire botTutor:say( "Just tap (or hold) the trigger to fire! Watch out, your weapons are hot!" ) scene.maxWaits = 30 while scene.hasShotHimself == 0 do if( scene.maxWaits > 0 ) then scene.maxWaits = scene.maxWaits - 1 if( scene.maxWaits <= 0 ) then botTutor:say( "Just tap the trigger...that BIG circle on the left. What could go wrong?" ) hiliteUI( HL_TRIGGER ) scene.maxWaits = 35 end end wait(1) -- we hang until he obeys end botTutor:say( "Sorry, I guess I could have warned you about that. Bullets bounce." ) waitOnDlg(0) hiliteUI( HL_TRIGGER ) botTutor:say( "That BLUE bit on the side of your trigger, shows your CHARGE." ) waitOnDlg(3) -- let them see it botTutor:say( "You need CHARGE to fire weapons, but your drone recharges directly from the Aether!" ) waitOnDlg( 1 ) -- STEERING TEST makeRepairs( myShipIndex, REPAIR_MASK_STEERING ) hiliteUI( HL_STEERING ) -- tecnically i can only highlight NAV which is steering and engines together wait(1) botTutor:say( "OK, we've got your steering working! The circle on the right is your NAV stick. " ) waitOnDlg(1) botTutor:ask( "Slide your NAV stick left or right to turn. Give it a try!" ) botTutor:say( "That RED bit on the side of your NAV stick, shows your ENERGY!" ) hiliteUI( HL_STEERING ) -- closest I can get for now waitOnDlg(3) -- let them see it botTutor:say( "Your drone will explode if it runs out of ENERGY, so try not to let that happen!" ) botTutor:say( "If nothing else, losing your drone means losing your progress on this map." ) waitOnDlg( 3 ) -- MAP INTRODUCTION makeRepairs( myShipIndex, REPAIR_MASK_MAPS ) wait( 1 ) botTutor:ask( "I think we have your hyperdrive working! But leave the STARMAP button alone for now, please!" ) botTutor:say( "The STARMAP button opens your Starmap Selector, connecting you to drones all over the galaxy!" ) botTutor:say( "But only starmaps you have collected and unlocked!" ) botTutor:say( "If you switch maps now, you will lose your progress on this tutorial." ) waitOnDlg( 3 ) -- VIEW CONTROLS botTutor:ask( "Drag three fingers to pan and tilt the view, or use two-finger pinch-zoom... Here's a top view" ) wait(6) -- request a camera change msec = 1800 -- take this many milliseconds to reach new value setCamPitch( CamPitchDown, msec ) -- looking straight down, mostly (avoids singularity) setCamHdg( CamHdgBehind, msec ) -- aligned with ship heading setCamZoom( CamZoomWide, msec ) -- waitOnDlg(0) botTutor:ask( "Give it a try, I can wait.. for a minute. Use 3 fingers to pan and 2 fingers to zoom." ) waitOnDlg(0) -- toss in the only CAM button instruction they will get makeRepairs( myShipIndex, REPAIR_MASK_CAM ) botTutor:ask( "Tap that CAMERA button (left of trigger) to get your camera lined up behind your drone!" ) waitOnDlg(3) botTutor:say( "OK, back to work! I'm resetting your camera now!" ) wait( 6 ) msec = 500 -- take this many milliseconds to reach new value setCamPitch( CamPitchDown, msec ) -- looking straight down, mostly (avoids singularity) setCamHdg( CamHdgBehind, msec ) -- aligned with ship heading setCamZoom( CamZoomWide, msec ) -- estimated zoom to show just the pen -- AIMING AT STATIONARY TARGET botTarget:enter( 0, -1 ) setCamSpy( botTarget.ship ) -- give him the camera wait( 4 ) setBeacon( beaconIdTarget, botTarget.ship, 1 ) botTutor:ask( "I've set up a target drone in the other side of your pen. Can you see it?" ) waitOnDlg( 5 ) setCamSpy( myShipIndex ) -- back to me, and leave it alone now botTutor:say( "See if you can hit that drone without hitting yourself!" ) while ( botTarget.numHits == 0 ) do -- remind him of the task botTutor:hint( 25, { "Use your NAV to aim for that gap in the wall.", "Try to bounce your shot into the drone, watch out for bounces back to you.", "Use pinch-zoom if you can't see the drone, or the gap in the wall", "Imagine where that wall would hit, if it didn't stop short. Aim for that spot.", "Just fire one shot at a time, until you're safely aimed. No sense shooting yourself!", } ) wait(1) end waitOnDlg( 0 ) botTutor:say( "Great shot, now destroy that thing!" ) while( botIsAlive( botTarget ) ) do wait( 1 ) end -- turn off beacon 1 (target beacon) setBeacon( beaconIdTarget, -1, 1 ) waitOnDlg( 3 ) botTutor:say( "Good Work, "..myShipName.."! You saved the world from that cardboard model" ) waitOnDlg( 3 ) botTarget:exit() -- no longer in game -- AIMING AT MOVING TARGET botTutor:say( "See if you can do it again with the default camera!" ) wait( 1 ) -- request a camera change msec = 2000 -- take this many milliseconds to reach new value setCamPitch( CamPitchSide, msec ) -- closer to horizon setCamHdg( CamHdgBehind, msec ) -- aligned with ship heading setCamZoom( CamZoomWide, msec ) -- estimated zoom to show just the pen waitOnDlg( 3 ) -- bring the drone back botTarget:enter( 0, -1 ) wait(1) setBeacon( beaconIdTarget, botTarget.ship, 1 ) botTutor:say( "Destroy that drone! Before it comes completely online!" ) -- after this many beats, the bot will wake up and hunt the player -- but since the player is held EXACTLY OPPOSITE it will just -- jam against the wall, and it is then possible to shoot it with -- enough reflections. This is a PUZZLE and might be too hard -- Take pity on them if they are struggling scene.numBeatsUntilWakesUp = 10 -- make a function to set up targets more easily -- note I can just add data willy nilly to the bot, so long as I -- don't read it before I write it. Here I add some flags and -- counters to detect when my orders have been followed by -- the player. These counters are then incremented in message -- handlers. while( botIsAlive( botTarget ) ) do if( scene.numBeatsUntilWakesUp > 0 ) then scene.numBeatsUntilWakesUp = scene.numBeatsUntilWakesUp - 1 if( scene.numBeatsUntilWakesUp == 0 ) then botTutor:say( "Too late! Watch out, it's alive!" ) -- put the bot into a more evil mindset -- currently, this is too hard, so just let -- the target remain dumb -- botTarget:behave( BEHAVE_MASK_HUNT, myShipIndex ) end else botTutor:hint( 25, { "Try shooting the wall behind you, a little off center.", "I don't know why the training program insists on you passing this test.", "It's not really representative of the skill set you'll need out there.", "But you'll never get out of that Pen until you destroy that drone. I know that much!", } ) end wait( 1 ) end wait( 10 ) -- time to admire the corpse (maybe this 'drops a power up' botTarget:exit( ) -- no longer in game TODO: Exit with explosion particles and sound setBeacon( beaconIdTarget, -1, 1 ) -- turn off target beacon botTutor:say( "You're doing great, " .. myShipName .. "!" ) waitOnDlg( 2 ) -- ENGINE TEST makeRepairs( myShipIndex, REPAIR_MASK_ENGINES ) hiliteUI( HL_STEERING ) wait( 1 ) botTutor:say( "OK, we've got your engines on line! That circle on the RIGHT is your NAV stick!" ) waitOnDlg( 0 ) hiliteUI( HL_STEERING ) botTutor:ask( "Push your NAV stick forward to apply thrust! Pull your NAV stick back to apply reverse thrust!" ) waitOnDlg( 3 ) -- STOP TEST makeRepairs( myShipIndex, REPAIR_MASK_STOP ) wait( 1 ) botTutor:say( "OK, your inertial dampers are repaired!" ) botTutor:ask( "Tap the STOP button to cancel all motion instantly! But watch out, it uses a lot of charge!" ) botTutor:ask( "Hold the STOP button for several seconds, to disconnect from your drone (pick a new color)" ) waitOnDlg( 3 ) -- PYRAMID PICKUP TEST makeRepairs( myShipIndex, REPAIR_MASK_PUPS ) wait( 1 ) botTutor:say( "OK, your powerup bar is repaired! This is where powereups appear after you collect them." ) botTutor:say( "You find powerups all over the galaxy. They look like little pyramids" ) botTutor:ask( "Just fly right over a powerup pyramid to pick it up. What's inside is a surprise!" ) botTutor:ask( "Once you have a powerup on the bar, just tap it to use it (or select it as new trigger weapon)." ) waitOnDlg( 1 ) botTutor:say( "I can also just give you some stuff. Here's a full recharge!" ) waitOnDlg( 1 ) givePup( 11 ) -- full restore wait( 6 ) --resetPup( myPupIndexHeal, seconds ) resetPupXY( myPupIndexHeal, 11, myPenPupX, myPenPupY, 1, 20 ) wait( 2 ) botTutor:say( "Try to pick up that powerup inside your pen! Meanwhile, we'll work on fixing your radar!" ) waitOnDlg( 0 ) -- RADAR (with a fake self destruct sequence to test the countdown timer and klaxon) wait( 5 ) makeRepairs( myShipIndex, REPAIR_MASK_RADAR ) hiliteUI( HL_RADAR ) wait( 1 ) botTutor:say( "OK! You should have a RADAR/COMPASS now! It's that big circle around your drone!" ) waitOnDlg(0) setCamZoom( CamZoomGroup, 100 ) -- we zooom in a little to make the radar bigger botTutor:ask( "The colored lines point to other ships. You can also see nearby beacons, zones and gravity wells" ) waitOnDlg( 3 ) botTutor:say( "Can you see me on your RADAR? I'll bring up another drone!" ) waitOnDlg( 1 ) -- bring back the target botTarget:enter( 0, -1 ) botTutor:say( "Fly around and see how the RADAR lines move!" ) botTutor:say( "Watch how the drone's RADAR line changes when I turn on its beacon!" ) waitOnDlg( 2 ) setBeacon( beaconIdTarget, botTarget.ship, 1 ) botTutor:say( "What's a beacon for? Well, might be a distress call. You gotta check it out!" ) botTutor:say( "That LONG line shows the direction of your motion, which might differ from where you're pointed!" ) waitOnDlg( 0 ) -- WEAPON SELECTION - MINES -- USING MINES (target drone is orbiting you, as it were, you lead it into a mine field) -- MISSILES (shows targetting target the tutor, then the drone) -- BARRIERS -- ZONES -- PODS -- TEAM INFO (map offers two team melee outside of the training pen) -- put up a score panel for num wins on each side, distributed state by moderator -- SOCIAL -- CAM button makeRepairs( myShipIndex, REPAIR_MASK_SOCIAL ) -- clean up botTarget:exit( ) -- no longer in game -- repair the ship completely botTutor:say( "Well, " .. myShipName .. ", you've really impressed all of us!" ) botTutor:say( "I think we can trust you to the next level!" ) makeRepairs( myShipIndex, REPAIR_MASK_ALL ) resetCamera() -- give them a token that let's them skip all the above setToken( TokenFinishedTutorial, 1 ) -- set to 0 to re-experience the tutorial -- Give the player some free gifts botTutor:say( "Here's some stuff you might find useful out there!" ) waitOnDlg( 3 ) givePup(3) -- mines botTutor:ask( "These mines are safe for you and your friends, but will explode when enemies approach them." ) waitOnDlg( 2 ) givePup(2) -- homing missiles botTutor:ask( "These Homing Missiles will seek out any target. In a pinch they will pick their own target." ) botTutor:say( "I know what you're thinking, don't even try it!" ) waitOnDlg( 2 ) givePup(22) -- encrypted starmap botTutor:ask( "You'll probably find some encrypted starmaps. Decrypt them, and you might find a new star system." ) waitOnDlg( 2 ) givePup(15) -- shields botTutor:ask( "These shields protect you from all weapon fire, but only for a few seconds." ) waitOnDlg( 2 ) -- take down the wall botTutor:ask( "Now that you know how to control your drone, what do you say? Still in it to win it?" ) botTutor:say( "OK! I'm opening your pen now, but you're always welcome back! I'll keep a full restore out for you!" ) waitOnDlg( 2 ) -- State 2 will fade out (to state 0) and the wall will animate out of existence setMapBarrierStateV( ixDoorBarrierV, 2 ) playSound( SOUND_BARRIER_CHANGE ) log(1," Finished: Learn the Controls") end, ----------------------------------- -- TASK: Help Klaus tut_HelpKlausFindPals = function( scene ) log(1," Starting: Help Klaus") -- PART 1 -- -- * we pick up just after the pen wall opens botTutor:enter( 0, -1 ) -- stationary mode with no targets wait(6) -- * WATTE gives us the back story and challenge botTutor:ask( myShipName .. ", I need you to keep your eyes open for escaped miners." ) botTutor:say( "They might lead us to the dread pirate, SLACK!" ) botTutor:say( "I'll be around somewhere, if you need me... WATTE OUT!" ) waitOnDlg( 2 ) -- have Watte actually fly off somewhere (and then disappear) -- and maybe the target drone should follow him like a pet botTutor:exit( 4000, 4000 ) -- head off in the direction of DALVIK --resetCamera() -- return to normal player-centric view -- TODO: some interesting delay before klaus talks to us wait( 25 ) -- * KLAUS spawns in/near the fuel depot botKlaus:enter(0, -1, glKlausX, glKlausY ) -- Do we do a cut scene for this? I mean cut camera to fuel depot for a second as he arrives? -- * Player gets KLAUS's distress call -- must follow beacon to find him botKlaus:ask("Hello? Can anyone hear us? Anyone USEFUL, I mean?") waitOnDlg( 0 ) freezePlayer() setCamSpy( botKlaus.ship ) botKlaus:ask("If you can handle it, rendevous behind the fuel depot. I'll turn on my beacon") wait(4) -- probably the first meainingful use of a beacon, to find somebody setBeacon( beaconIdKlaus, botKlaus.ship, 1 ) waitOnDlg(0) releasePlayer() resetCamera() -- return to normal player-centric view -- * Player rendevous with KLAUS, an escaped miner -- must get close to Klaus (and engines freeze) -- onBEACON( minDist, maxDist ) - test local ship and remember state and report state changes -- * Klaus begs us to rescue N escaped miners -- ship is released from 'docking' wait( 3 ) -- jam here until the player gets close enough to Klaus, then freeze the player -- Note: we pass an array of 'hints' that are played periodically, in order, cyclically -- until the player completes the task. I do this a lot :-) scene:waitUntilPlayerNearBot( botKlaus, 200, 500, { "Just follow my beacon and get real close to my ship, real slow. Then we'll lock on.", "You're going to need access to my air lock, if this is to work.", "Your ship should stop automatically once you meet the profile... slow and close.", "Otherwise... Well, good thing we're near a clone factory, right?", "In the center of each factory is a clone reactor core. It's powered by three crystals." } ) -- stop his ship setShipVel( myShipIndex, 0, 0 ) freezePlayer() -- zoom in for this contact --setCamSpy( botKlaus.ship ) waitOnDlg( 1 ) setCamZoom( CamZoomCloseUp, 1000 ) -- so you just got close to Klaus, he greets you and explains his problem botKlaus:ask("Thanks for coming, " .. myShipName .. "! My name is KLAUS.") botKlaus:say("I stole this ship from SLACK, when we escaped his mines!") botKlaus:say("But... we got split up.. and some of my pals got left behind.") botKlaus:say("They're still out there, disguised as powerups so SLACK doesn't find them!") botKlaus:say("Can you help me rescue my pals? Just pick 'em up, and bring 'em to me here..") botKlaus:say("I know it's a lot to ask of a stranger, but these are hard times.") waitOnDlg( 2 ) -- i want to start the camera zoom out at the same time this last message appears botKlaus:ask("I have to wait here, at our meeting point. Just bring me my pals!") releasePlayer() resetCamera() waitOnDlg( 2 ) -- wait for user to clear that last one -- -- PART 2 -- -- (game starts spawning escaped-miner pups, you can pick up as normal) -- (they stack on your button bar, up to N) scene:startEjectPilotDetector( pupMiner ) -- increments rescued counter when pup is 'used' -- * Player rescues N escaped miner (powerups) and returns to KLAUS -- * has to rendevous again, locking ship again -- * Clicking miner pups near KLAUS transfer them to his ship -- * He says something witty and/or encouraging for each one -- they will be spending quite awhile in this probably scene:waitForAllMiners() -- does not get here until all miners have been rescued setToken( TokenFoundPals, 1 ) -- I am of two minds, cleaner to grant on exit, but just in case they crash... -- at this instant, we know they are close to SLACK, so let's freeze them now -- but not announce it yet local mask = REPAIR_MASK_ENGINES -- and disable engines and weapons + REPAIR_MASK_TRIGGER setShipRepairMask( myShipIndex, mask ) -- TODO: ship shutdown sound setShipVel( myShipIndex, 0, 0 ) -- hint that something is not right playSound( SOUND_DISENGAGE ) -- * On receipt of Nth miner, botKlaus:ask("Smitty! I'm so glad to see you! Thanks, " .. myShipName .. ", you rounded up all my pals!" ) -- * sound effect, player ship functions fail (weapons, steering, engines) -- * KLAUS turns out to be SLACK in disguise waitOnDlg( 2 ) botKlaus:ask("There's just one problem... My name isn't Klaus!" ) wait(1) -- 14 incoming chat -- 16 metronome -- 35 - 82 percussive instrument hits -- 200 plot point -- 201 bongos transition -- 202 conclusion -- 203 message box -- 204 button click -- 205 chime 'dong' playSound( 200 ) -- Major Plot Point setCamZoom( CamZoomCloseUp, 1000 ) -- close up on player (with Klaus nearby) waitOnDlg( 0 ) botKlaus:exit() -- klause disappears botSlack:enter( 0, -1, glKlausX, glKlausY ) -- slack appears in his place botSlack:ask("My name is SLACK, as in 'dread space pirate SLACK'!" ) botSlack:say("I believe that means YOUR name is 'idiot'!" ) botSlack:say("I especially thank you for recovering my minions after WATTE sent them flying!" ) botSlack:ask("I think you'll find your drone is quite broken. I have.. plans.. for you!" ) botSlack:say("For now, just sit back and watch! I have him on screen now..." ) waitOnDlg( 5 ) -- * SLACK takes back the miners and is about to destroy us -- -- PART 3 -- -- * but WATTE reappears and saves Player, -- WATTE and SLACK can't be on screen at same time, so -- slack is about to shoot us -- we see RADIO from WATTE saying he is GOING to help us -- TODO: needs a bit more suspense, and sound effects botTutor:ask( myShipName .. "! I saw an energy discharge, are you OK? I'm on my way!") waitOnDlg( 2 ) -- WATTE starts from our clone factory, and then makes a bee line towards SLACK botTutor:enter( 0, -1, glHomeX, glHomeY ) botTutor:go( botSlack.ship ) -- SLACK says this to us, presumably WATTE doesn't hear it. can I indicate whisper? botSlack:say( "on his way... to my TRAP, that is! And YOU'RE the BAIT!" ) -- SLACK leaves to set a trap for WATTE waitOnDlg( 2 ) botSlack:go( botTutor.ship ) -- We are still disabled, but we hear it all on radio as they battle it out -- We can hear them on the radio even though their ships are actually hidden botTutor:say("I see you up ahead! I'm almost there! Why don't you respond, " .. myShipName .. "?") botTutor:say("There you are! No wait, what's that?") botSlack:say("Surprised, WATTE? You shouldn't be!") botTutor:say("SLACK! You've escaped!") botSlack:say("Yes, with " .. myShipName .. "'s foolish help! Along with all my minions!") waitOnDlg( 2 ) -- pauses to think botTutor:say("Looks like you win THIS round, SLACK") botSlack:say("Too bad it's your LAST round, WATTE!") waitOnDlg( 2 ) -- some attack we can't see. TODO: sound effect botTutor:say("I doubt it. You'd need some sort of coupled energy beam to make ME worried!" ) waitOnDlg( 0 ) playSound( SOUND_ENGAGE ) botSlack:say("About that... ") botTutor:say("NO!!!.. not that... ... good luck, " .. myShipName .. " ... " ) -- After awhile, our systems come back online waitOnDlg( 5 ) -- restore engines and weapons setShipRepairMask( myShipIndex, 0 ) playSound( SOUND_ENGAGE ) wait(2) resetCamera() -- we don't see it, but SLACK skulks off to his clone factory at start of next section botSlack:exit() -- and WATTE teleports to our hacked clone factory at the start of the next -- section, but for now we wink him out botTutor:exit() waitOnDlg( 15 ) -- time to be worried about WATTE before starting next task log(1," Finished: Help Klaus") end, ------------------------- -- TASK disable clone reactor tut_DisableCloneReactor = function( scene ) log(1," Starting: Disable Reactor") -- put SLACK in home zone botSlack:enter(0, -1, glEnemyHomeX, glEnemyHomeY ) -- he is just eyecandy until repaired -- Put WATTE in home zone botTutor:enter(0, -1, glHomeX, glHomeY ) -- but moved to Team home zone botTutor:ask( myShipName .. "... We've hacked... one of his... clone.. factories. Meet me... there..... beacon... " ) wait(1) setBeacon( beaconIdTutor, botTutor.ship, 1 ) -- end of the off-screen battle, time for player to do something. -- do we have a Jarvis to tell us we're back on line (I want that to be cambot)? -- bases get configured (enemy base starts getting dangerous) -- We rendevous with WATTE, only to see his drone explode -- this routine doesn't return until the player has 'docked' with -- the target bot. (They have to get close, at low speed). They -- will then be stopped (but not frozen). -- The array of hint messages will be seen cyclically until they complete -- the task scene:waitUntilPlayerNearBot( botTutor, 200, 500, { "I'm in bad shape, but he's hurt, too. He's licking his wounds in his Clone Reactor!", "I'm sorry I blamed you, ".. myShipName ..". It wasn't your fault", "You weren't ready... I failed in your training.. It's my fault", "He's in no shape to stop you from stealing his crystals, but he'll have minions nearby.", "I'm sorry I won't be there for you, when you need me the most.", "You can't let him get fully repaired, or there will be no stopping him!", "You remember how to follow a beacon, right?" } ) -- stop his ship setShipVel( myShipIndex, 0, 0 ) freezePlayer(); -- we have arrived at WATTE in our home Factory waitOnDlg( 0 ) setCamZoom( CamZoomCloseUp, 1000 ) -- zoom on US, but we're close to WATTE -- WATTE 'dies' botTutor:ask( "OK, " .. myShipName .. " here's the deal. You're gonna have to do this on your own." ) waitOnDlg(0) botTutor:say( "There are two Clone Factories in this Star System. This is one of them, the one I hacked." ) setCamToOrbitSpot( glHomeX, glHomeY, CamZoomGroup ) botTutor:say( "Inside this reactor, you're safe. The walls reflect weapon fire." ) waitOnDlg(4) botTutor:say( "But there's another reactor, the one where SLACK is now." ) wait(3) setCamToOrbitSpot( glEnemyHomeX, glEnemyHomeY, CamZoomGroup ) botTutor:say( "They look identical, except for the colors. You can fly right through the walls." ) botTutor:say( "Also, there's a negative energy field, draining you while in the core." ) botTutor:say( "In the center is the Core Crystal, which the HUD shows as a FLAG, red or blue." ) botTutor:say( "You need to steal that crystal, and bring it back here." ) waitOnDlg(0) wait(2) botTutor:say( "Then go back twice more for the others.." ) waitOnDlg(0) setCamToOrbitSpot( glHomeX, glHomeY, CamZoomGroup ) botTutor:say( "Take out his minions first... but try not to hurt Smitty!" ) botTutor:ask( "Remember... THREE crystals... look like flags... pick up THERE.. and drop off HERE." ) waitOnDlg(2) setBeacon( beaconIdTutor, -1, 1 ) -- beacon off (symbolizes dying) botTutor:say( "Do it for ME, " .. myShipName .. ". You owe me, you know. You KNOW it." ) waitOnDlg( 2 ) botTutor:say( "WATTE.... out.... " ) wait(3) -- OK, try to make his ship explode setShipState( botTutor.ship, SHIP_STATE_EXPLODING ) wait(3) botTutor:exit() -- hopefully, when he re-enters, he will get new state automatically --setCamZoom( CamZoomWide, 1000 ) releasePlayer() resetCamera() -- * WATTE appears to have been destroyed (sad music) -- TODO: make WATTE play the explode (can I set state?) waitOnDlg( 2 ) -- * Meanwhile, SLACK has escaped to his home base, which then gets defensive minions -- we are held in protective custody by home base, fully healed -- SLACK taunts us, then the camera follows him back to his home -- we see his corner tower defenses get built (he brags on radio) -- he disappears, so we just see the zone and flag as normal, plus towers -- TODO: somehow keep SLACK alive during the stray weapon's power.. -- TODO: also use invulnerability to keep player safe from griefers while in dialog -- spawn the minion guards on top of SLACK and let them sort it out -- but I now declare that the factory square always holds 4 nodes (as many as nine maybe) -- which should be emphasized for RTS factory emulation botMinion1:enter(0, -1 ) log(1,"minion1 finished entering") botMinion1:guard( botSlack.ship ) log(1,"minion1 done starting guarding") botMinion2:enter(0, -1 ) log(1,"minion2 finished entering") botMinion2:guard( botSlack.ship ) log(1,"minion2 done started guarding") -- -- Part 4 -- -- * If a tower is left alone (not destroyed), it can tear off and be a free-flying minion that chases Player -- towers respawn after some number of seconds -- 5 seconds as non-shooting, but hard to kill -- 60 seconds of stationary tower, limited range and firepower -- then tears off to be a minion and can chase Player) -- * Player destroys towers faster than they are rebuilt or tear off -- I am changing my mind on the towers and instead will just have minion bots -- who have AI that nav targets SLACK, so they try to orbit him. Might need -- a GUARD nav where the goal is to point away from target instead of towards -- target -- it's nice to have a beacon kinda pointing at the next thing you need to do setBeacon( beaconIdSlack, botSlack.ship, 1 ) -- trash talk until he gets close, but not too close scene:waitUntilPlayerNearBot( botSlack, 600, 500, { -- getting anywhere near close the first time "Oh boo hoo. You owed him NOTHING! Nor did I!", "He was not the man you thought he was. Not the man at all.", "Just wait until my clone reactor repairs my engines! And my weapons!", "Enjoy what are surely your final moments in this star system, " .. myShipName } ) -- I do NOT stop him -- but that's it, no more auto-stop when approaching Slack stopTrigger( 1 ) -- some final invective before the fight starts -- maybe have the 'towers' appear one by one -- this is also how we know you approached close enough botSlack:say( "Smitty! Load a torpedo for me, and write '".. myShipName .. "' on the side!" ) waitOnDlg(2) setShipTgt( botMinion1.ship, myShipIndex ) -- they both hate the local player now setShipTgt( botMinion2.ship, myShipIndex ) -- handle the battle of the Clone Reactor -- returns when three flags have been successfully captured -- miniature capture the flag game scene:waitForReactorFailure() -- doesn't get here until player completes task, and we will -- grant token on return log(1," Finished: Disable Reactor") end, ---------------- -- TASK unmask Slack tut_UnmaskSlack = function( scene ) log(1," Starting: Unmask Slack") -- Slack should still be in his clone reactor botSlack:exit() -- we exit and re-enter, just in case you killed him by accident botSlack:enter(0, -1, glEnemyHomeX, glEnemyHomeY ) -- he is just eyecandy until repaired setBeacon( beaconIdSlack, botSlack.ship, 1 ) botSlack:say( "CURSES! You've destroyed my Clone Factory and all my minions! ... even Smitty!" ) botSlack:say( "But you have yet to defeat ME, and now I return to FULL STRENGTH!" ) -- trash talk until he gets close, but not too close scene:waitUntilPlayerNearBot( botSlack, 600, 500, { "Haven't you heard? This Star System is MINE!", "By the juice of Zaphod, I will control the spice!", "You know, WATTE, at least, showed some class.", "WATTE was actually a pretty swell guy, and I regret killing him.", "It must be hard on you that he blamed you to the end.", "I mean, after all, things were going pretty well for WATTE before YOU showed up.", } ) -- I do NOT stop him -- player frozen in home zone, gets recharge, watchs cut scene with camera work -- SLACK gets the first whack -- TODO: engine AI should not pull trigger, if inside no shooting zone botSlack:attack( myShipIndex ) -- NOW it's a fight botSlack:say( "THAT was for Smitty!" ) -- Part 5 -- -- * enemy base becomes safe-ish (no towers) -- * but SLACK appears, and any remaining tear-off minions -- * battle until all bots are destroyed -- maybe slack is invulnerable/deferred until you destroy minions -- Trash talk during boss fight -- The big boss fight scene:waitForBossDead( botSlack, { "Is that what you call a weapon? How cute!", "Seriously, I don't mean to pry, but are you sure you're cut out for this hero business?", "My mom wanted me to be a doctor. Mom's love having doctors in the family.", "Good old mom. Terrible cook, but excellent mother.", "That was surprisingly painful, I must admit. I think you're getting better!" } ) setToken( TokenUnmaskedSlack, 1 ) -- just in case, we grant token early -- * when SLACK is destroyed, he reveals he was WATTE all along -- * you pass the test -- TODO: need some camera work here, and a way to position -- everyone (fade to black, move everyone?) botSlack:say( "OK.. OK.. I think you've proven your point" ) botSlack:say( "No.. really.. you can stop shooting now. " ) botSlack:say( "Seriously, those aren't real weapons." ) botSlack:say( "You think we'd trust a raw recruit with real weapons?" ) waitOnDlg( 2 ) botSlack:say( "Oh sorry, you see. I'm not SLACK" ) playSound( 200 ) -- Major Plot Point -- TODO: closeup on slack? -- we need to freeze slack's ship, and grab his xy, so we can teleport watte to the same spot local si = botSlack.ship setShipVel( si, 0, 0 ) wait(2) local x = botSlack.spawnX local y = botSlack.spawnY if ( glShips[ si + 1 ] ) then x = glShips[ si + 1 ].x -- from cache, should be 'close' y = glShips[ si + 1 ].y end waitOnDlg(2) botSlack:exit() botTutor:enter(0, -1, x, y ) --setShipPos( botTutor.ship, x, y, 0 ) botTutor:say( "I'm WATTE, your tutor! And this has all just been part of your training!" ) botTutor:say( "Smitty is fine, by the way, and at his daughter's dance recital" ) botTutor:say( "Sorry we had to be so rough on you, but this is important stuff!" ) botTutor:say( "We have to be SURE you can handle it, before we really let you go." ) waitOnDlg( 2 ) log(1," finished: Unmask Slack") end, ----------------------------------------------------------------------- -- SCENE HELPER FUNCTIONS --------- -- waits indefinitely for the player to approach a specific ship -- The idea is to declare a beacon with a radius and duration -- the game engine will then send us a message if the player ever -- remains inside that zone for that duration, we catch that -- message and set a global, which we clear here before we start -- waiting senses = {}, -- i think I have to predeclare this waitUntilPlayerNearBot = function( scene, bot, dist, msec, hints ) local senseIndex = 1 -- in theory we have an array of these scene:startDistDetector( senseIndex, bot, dist, msec, "uGotCloseToKlaus", "uGotFarFromKlaus" ) scene.senses[ senseIndex ] = 0 -- clear old value while ( scene.senses[ senseIndex ] == 0 ) do bot:hint( 25, hints ) wait( 1 ) end end, -- we have to ask the game engine to notify us when the player's ship reaches -- its scripted destination. -- The engine increments a 'sense counter' when the required trigger is met. -- Coroutines then look at those counters, and reset them, as needed startDistDetector = function ( scene, senseIndex, bot, dist, msec, uMsg, uMsg2 ) local senseIndex = 1 -- TODO: pass this in args -- actually add a new message handler, dynamically. wewt. -- this message is sent when player gets close scene.handler[ uMsg ] = function( scene, args ) log( 1, uMsg .. " handler in scene id ".. scene.id .. " was called" ) bot.closeToPlayer = 1 -- player is currently close to this bot if ( scene.senses[ senseIndex ] ) then -- increment the sense and let the caller work out what to do scene.senses[ senseIndex ] = scene.senses[ senseIndex ] + 1 -- ok, maybe we also force player to stop, just to make -- rendevous less of a hassle, but script must immediately -- disable player's controls, if the goal is to hold them -- fixed in space. --setShipVel( myShipIndex, 0, 0 ) end end -- this message is sent when player gets 'distant' (double the sense radius) scene.handler[ uMsg2 ] = function( scene, args ) log( 1, uMsg2 .. " handler in scene id ".. scene.id .. " was called" ) bot.closeToPlayer = 0 -- no longer close end -- BUT we won't get EITHER of those until we declare a trigger. -- Only then will the game engine track the relationship. -- 1 = distance check from ship, send message when trigger hits setTrigger( 1, bot.ship, dist, msec, senseIndex, uMsg, uMsg2 ) end, -- This guy senses message sent when an escaped miner pup is 'used' by the player -- (meaning they tapped the PUP button on their PUP bar). In theory this launches -- the miner out some tube, and should only be done while near KLAUS, who has an -- open airlock. In actuality, it just increments the number of miners that -- have been rescured. the 'closeToPlayer' field is ONLY set by those trigger -- messages above, and is not some universal thing that's always available for -- all bots. startEjectPilotDetector = function( scene, pup ) -- we want to turn a message about the local pilot using a scripted powerup, into -- some counter changes and maybe some radio scene.handler[ pup.useMsg ] = function( scene, args ) log( 1, pup.useMsg .. " handler in scene id ".. scene.id .. " was called" ) if (botKlaus.closeToPlayer == 0) then -- no credit if not near Klaus botKlaus:ask("Dude! Don't just VENT guys to space! You have to be close to ME when you do that!") botKlaus:say( "Just approach me slowly and you will auto-stop when in position. Then tap the pup slot" ) else -- give credit, but no chat since we're not a coroutine scene.numMinersRescued = scene.numMinersRescued + 1 end end end, ------------ -- does all the logic of sensing your collection -- of escaped miners and does not return until that action is -- complete waitForAllMiners = function( scene ) -- this variable gets incremented by a message handler scene.numMinersRescued = 0 -- none saved yet scene.totMinersNeeded = 5 -- we need to rescue this many scene.lastNumMinersRescued = 0 -- so we detect changes log(1, "Starting search for " .. scene.totMinersNeeded .. " escaped miners") while ( scene.numMinersRescued < scene.totMinersNeeded ) do if( scene.numMinersRescued > scene.lastNumMinersRescued ) then scene.lastNumMinersRescued = scene.numMinersRescued local remaining = scene.totMinersNeeded - scene.numMinersRescued if( scene.numMinersRescued == scene.totMinersNeeded ) then botKlaus:say( "You did it! You found all my pals... even Smitty!" ) elseif ( scene.numMinersRescued < scene.totMinersNeeded ) then botKlaus:say( "Way to go, ".. myShipName .. "! Only " .. remaining .. " more to find!" ) else botKlaus:say( "Dude! That's not one of MY guys! Throw him back, quick!" ) end else botKlaus:hint( 25, { "My pals are probably all over this star system by now.", "Their suits hold enough air for a while, but the smell builds up pretty fast.", "You'll want to bring them back to me as fast as you can.", "Did I mention they are disguised AS POWERUPS? Yeah, the little pyramids.", "I think I saw one headed inside the Fuel Depot. But be careful in there. Unshielded panels.", "It's kinda fun in the depot, if you're into that sort of thing.", "After you pick them up, you'll need to sort of SHOOT them into my airlock.", "Be sure you're close to my ship when you release them.", "I'll try to keep an airlock open to catch them with." } ) end wait(1) end end, ----------- -- doesn't return until you have stolen N clone reactor crystals -- * Player dashes in, grabs flag, and takes it to home zone, does that N times waitForReactorFailure = function( scene ) -- initializes scene.numReactorCrystalsStolen = 0 scene.numReactorCrystalsNeeded = 3 scene.numReactorCrystalsStolenLast = 0 -- just in case you have been messing around, bring them back now resetPup( 249, 3 ) resetPup( 250, 3 ) -- hijack the flag message handler for this scene -- the engine only sends this when ship is in a team-friendly zone, -- we just want to know whose flag they had scene.handler['onFLAG'] = function( scene, args ) local ship = 0 + args['ship'] -- 0-7 local flag = 0 + args['flag'] -- likely 249 (west) or 250 (east) local zone = 0 + args['zone'] -- likely 9 (west) or 10 (east) local zoneTeam = 0 + args['team'] -- likely to be 0 (none), 9 (West), or 10 (East) log( 1, "saw onFLAG message. Ship " .. ship .. " dropped flag " .. flag .. " in zone " .. zone .. ", zTeam: " .. zoneTeam ) if( zone >= 0 and zone < 100 ) then -- we need to collapse 8 players into two teams local shipTeam = (ship % 2) -- Here, i prefer 0 = west, and 1 = east, (ship is 0-7 here) local flagTeam = ((flag+1) % 2) -- 249 -> 0, 250 -> 1 if ( shipTeam ~= flagTeam ) then -- and only when enemy flag log(1, "Flag dropped in its opponents zone. myShipIx is: " .. myShipIndex ) if (ship == (0+myShipIndex)) then log(1, "Flag was dropped by our player and earns credit" ) -- just increment when it was done by our player scene.numReactorCrystalsStolen = scene.numReactorCrystalsStolen + 1 log(1, "numXtals Stolen = " .. scene.numReactorCrystalsStolen ) end end end -- all paths need to restart the flag pup resetPup( flag, 3 ) end local smittyWasAlive = 1 -- assume he started alive -- wait for goal, hint/taunt while (scene.numReactorCrystalsStolen < scene.numReactorCrystalsNeeded ) do if( scene.numReactorCrystalsStolen > scene.numReactorCrystalsStolenLast ) then local remaining = scene.numReactorCrystalsNeeded - scene.numReactorCrystalsStolen scene.numReactorCrystalsStolenLast = scene.numReactorCrystalsStolen if( remaining > 1 ) then botSlack:say( "Fool, I still have " .. remaining .. " reactor crystals! More than I need!" ) else botSlack:say( "Even this LAST crystal is more than I need, to defeat a worm like YOU!" ) end else if (smittyWasAlive == 1) then if( not botMinion1:alive() ) then smittyWasAlive = 0 -- farewell smitty, we barely knew ye botSlack:say( "Smitty! You've destroyed Smitty! You MONSTER! You will PAY!" ) end end botSlack:hint( 25, { "To defeat me, you'd have to remove ALL the power crystals from my clone reactor!", "Plus, you'd have to carry each one, all the way back to your hacked clone reactor!", "If you even try it, The core of the reactor itself will burn you!!", "If my minions don't burn you first!!", "Fool, this is a CLONE FACTORY! Anything you destroy, I can rebuild!" } ) end wait( 1 ) end -- * base is destroyed, no more towers spawn, end, ------------ -- doesn't return until you have killed bot -- boss comes to life, no longer invulnerable, no longer stationary -- boss picks player as target and begins hunting -- lots of trash talk -- maybe a special weapon use -- otherwise, just battle until its beaten (boss is stronger than normal ship) waitForBossDead = function( scene, bot, hints ) bot:say( "Prepare yourself!" ) bot:attack( myShipIndex ) -- at this point he wants to hurt me, and will actively seek me while( botIsAlive( bot ) ) do if( nil ) then -- maybe react at partcular energy levels or something else -- otherwise, just periodically hurl a 'hint' (taunt, more likely) bot:hint( 35, hints ) end wait( 1 ) end wait( 10 ) -- time to admire the corpse (maybe this 'drops a power up' end, -- return to standard player-centric camera -- keep the game options MENU up to date updateDisplays = function( scene ) -- this one is always there option(1, 0, "uRESET_TUTORIAL", "Reset Tutorial Progress" ) end, ---------------------------------- -- optional SCENE message handlers handler = { -- while I do not provide my own message handlers for -- onAWARE and onLAUNCH, I do override the cutscenes played -- by both of those. -- note that BOTs get their own notifications, this is the SCENE -- I use the SCENE notifications to update local state booleans the -- SCENE cares about (and the bot might have no idea over) -- However, in this process, I add state to the bot, that it doesn;t -- know anything about, and there is some danger I could step on the -- toes of another scene. This is just something you, the author, need -- to watch out for, the same as you allocating ship indices or barrier -- indices. It's your job. Luckily, the namespace is just this one -- starmap, so you shouldn't have too much trouble! I suggest making -- global values where to assign these shared resources all in one spot -- of the file, though that goes against my goal of each scene being -- easily copied and pasted (even more important in Lua, since there is -- no easy and obvious "missing variable" notification. You just get 'nil' -- and probably throw an exception, stopping the coroutine without any -- real error info.) -- Hmm, one could probably make an API emulator that ran in a pure lua -- environment, where the author could test their syntax in the presence -- of a good editor (one that detects missing commas in table entries, for example!) ['onDEAD'] = function( scene, args ) log( 1, "onDEAD handler in scene id ".. scene.id .. " was called" ) log( 1, "onDEAD ship ".. args.ship .. ": " .. args.name .. " killed by " .. args.killer .. ": " .. args.killerName ) if (args.ship == myShipIndex and args.killer == myShipIndex ) then if( scene.state == STATE_LEARN_TRIGGER ) then botTutor:ask( "Oops, " .. myShipName .. ", you've destroyed your own drone!" ) botTutor:ask( "Go ahead and launch a new drone. We'll try again!" ) end scene.hasKilledHimself = scene.hasKilledHimself + 1; end end, ['onDAMAGE'] = function( scene, args ) --log( 1, "onDAMAGE handler in scene ".. scene.id .. " was called for ship: " .. args.ship -- .. " attacker: " .. args.attacker ) if ( (args.ship == myShipIndex) and (args.attacker == myShipIndex) ) then if( (scene.state == STATE_LEARN_TRIGGER) and (scene.hasShotHimself == 0) ) then botTutor:ask( "Watch out, " .. myShipName .. "! Bullets bounce off of most barriers! And your own weapons can hurt you!" ) end scene.hasShotHimself = scene.hasShotHimself + 1; end end, ['uRESET_TUTORIAL'] = function( scene, args ) log( 1, "uRESET_TUTORIAL handler in scene id ".. scene.id .. " was called" ) areYouSure( "Forget all progress on this starmap, so you can redo it from the start?", "FORGET ALL", "uRESET_DOIT" ) end, ['uRESET_DOIT'] = function( scene, args ) log( 1, "uRESET_DOIT handler in scene id ".. scene.id .. " was called" ) -- actually do the deed when we get this frm the areYouSure botTutor:ask( "Your permanent record has been wiped clean! Now re-enter the sector, please." ) setToken( TokenFinishedTutorial, 0 ) -- strip them setToken( TokenFoundPals, 0 ) -- strip them setToken( TokenDisabledReactor, 0 ) -- strip them setToken( TokenUnmaskedSlack, 0 ) -- strip them -- they have to actually relaunch to see everthing change, maybe I should do something end, }, -- end of handler table }) -- end of scene table -- paranoiacally log some of these to make sure inheritance works log( 1, "------SCENE ROOT --------" ) dumpTable( sceneRoot, "SceneRoot ") log( 1, "------SCENE MAIN --------" ) dumpTable( sceneMain, "SceneMain ") ------------------------------------------------------------------------------- -- Scripted PowerUps used on this map -- n escape miner (disguised as a powerup) pupMiner = { bundleName = "an Escaped Miner", desc = "Escapee from the Improovium mines of DALVIK, take to KLAUS asap!", numInPack = "1", maxCanOwn = "5", useMode = 2, -- 0:idle 1:onPickup 2:onTap 3:onTrigger useMsg = "uEjectedMiner", -- msg to be sent when local player uses one iconId = 30, wpn = { wpnName = "Escaped Miner", usesAmmo = 1, singleShot = 1, homing = 0, needsTarget = 1, autoRepeatMsec = 1000, lifetimeMsec = 20000, speed = 5, power = 23, soundLaunch = 15, soundContact = 16, soundFizzle = 17, shotsPerFullCharge = 20, }, } log( 1, "------Test Powerup --------" ) dumpTable( pupMiner, "pupMiner ") --============================================================================= -- stuff I do once when map is loaded, preferably nothing. But if I did -- need to precompute some tables, this would be the place to do it. -- In this map, I work out the dimensioins of the eight training pens, -- but remember this happens before the player gets here, so you don't -- know which slot they will pick. log( 1, "----- Modifying StarMap Geometry --------" ) -- I should really use some decoration for these globals, like 'glSide' instead of just 'side' border = 256 -- stay away from edge of galaxy glPenSide = 1000 -- side length of each pen rad = glPenSide/2 -- 'radius' of the pen vgap = rad -- vert dist between pens cx = 4000 cy = 4000 ixTopLine = 20 ixLeftLine = 20 ixDoorBarrierV = 20 -- this can open/close -- I try to keep a little info around for all the pens, stuff I can -- know before launch of any ships glPenPupX = {} glPenPupY = {} function getPenDimensions( ix ) -- work out the interesting coordinates row = math.floor(ix/2) -- 4 'rows' of pens -- cx = border + glPenSide/2 if (ix%2) == 1 then cx = 8191 - (border + glPenSide/2) end cy = 4096 - (row-2) * (glPenSide + vgap) - (glPenSide+vgap)/2 --cy = cy - (row * (glPenSide + vgap)) -- allocate some indices, we use more than one ixTopLine = 20 + ix * 4; ixLeftLine = 20 + ix * 4; ixDoorBarrierV = ixLeftLine if (ix%2) == 0 then ixDoorBarrierV = ixLeftLine + 1 -- I want the right line, for the left pens, open towards the star end end -- this happens BEFORE launch, so no player info function buildTrainingPen( ix ) getPenDimensions( ix ) -- work out some constants local pain = 0 local xpar = 0 local color = ix + 1 local state = 1 local left = cx - rad local right = cx + rad local top = cy + rad -- y increases upwards local bottom = cy - rad local f = glPenSide / 10 -- size of navigation gap setMapBarrierH( ixTopLine, state, left, right, top, color, xpar, pain ) -- top line (north) setMapBarrierH( ixTopLine+1, state, left, right, bottom, color, xpar, pain ) -- bottom line setMapBarrierH( ixTopLine+2, state, left+f, right-f, cy, color, xpar, pain ) -- mid line setMapBarrierV( ixLeftLine, state, bottom, top, left, color, xpar, pain ) -- west side setMapBarrierV( ixLeftLine+1, state, bottom, top, right, color, xpar, pain ) -- east side -- some ship stuff x = cx y = cy - rad/2 hdg = 0 setMapSpawn( ix, x, y, hdg ) -- the pup spawn location glPenPupX[ix+1] = cx - rad/2 glPenPupY[ix+1] = cy - rad/2 end -- this happens AFTER launch, so customize for THIS player -- it leaves behind some stuff valid for My Ship Only myPupIndexHeal = 100 -- 8 of these, one for each player myPupIndexMiner = 110 -- 8 of these, one for each player myPenCX = 0 -- eventually, the center of my pen myPenCY = 0 myPenRadius = 0 myPenYLow = 0 -- Y of south end of pen myPenYHi = 0 -- Y of north end of pen myPenPupX = 0 -- where my private pen PUP will go myPenPupY = 0 function forceShipToStart( ix ) -- force his ship to hold in position tgt = -1 -- take away any target he had mask = REPAIR_MASK_ALL -- and disable all his controls setShipRepairMask( ix, mask ) setShipTgt( ix, tgt ) -- set up a target dummy spawn point getPenDimensions( ix ) -- remember my personal settings myPenCX = cx myPenCY = cy myPenYLow = cy + rad/2 myPenYHi = cy - rad/2 myPenRadius = rad -- where my personal pup will spawn myPenPupX = cx - rad/2 myPenPupY = myPenYHi -- where my personal training drone will spawn botTarget.spawnX = cx botTarget.spawnY = myPenYLow -- and our tutor will show up here, outside our Pen botTutor.spawnX = cx + rad * 1.2 if (ix % 2) == 1 then botTutor.spawnX = cx - rad * 1.2 end botTutor.spawnY = cy + rad/2 -- pup spawn slots myPupIndexHeal = 100+ix -- each player gets one, in their pen myPupIndexMiner = 110+ix -- some number of these are randomly seeded -- declare the escaped miner PowerUp (adds new pup to game engine tables) wpnId = 1 -- TODO: make it so you actually launch a projectile (a little astrpnaut, ideally) setPowerUp( PupIdMiner, wpnId, pupMiner ) -- these only take effect when I am moderator, but I need to declare them -- or they cannot be reset later resetPupXY( myPupIndexMiner, PupIdMiner, -1, -1, 0, 20 ) resetPupXY( myPupIndexMiner+1, PupIdMiner, -1, -1, 0, 20 ) resetPupXY( myPupIndexMiner+2, PupIdMiner, -1, -1, 0, 20 ) resetPupXY( myPupIndexMiner+3, PupIdMiner, 100, 100, 0, 20 ) -- inside the fuel depot? -- ditto for the per-pen recharges local i for i=1,8 do resetPupXY( 100+i-1, 11, glPenPupX[i], glPenPupY[i], 1, 20 ) end -- and the flags? resetPup( 249, 3) resetPup( 250, 3) end -- build all 8 training pens (not knowing which is ours) function buildMapGeometry() for i=1,8 do -- for each possible player ix = i - 1 -- I want 0 to 7 here buildTrainingPen( ix ) end end -- now invoke that buildMapGeometry() -- let the log know we're done loading the starmap log(1, '------ Tutorial Starmap Script Loaded ------' ) -- end of script