;--[[ ; 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 ; The game is available in the Google Play Store, and for Kindle Fire ; ; https://play.google.com/store/apps/details?id=com.synthetic_reality.synspace&hl=en ; ; Windows Version (c) 2001 synthetic-reality.com all rights reserved ; Android Version (c) 2013 synthetic-reality.com all rights reserved ; ; ----- ; ; "Defense of the Clones" ; ; (c) 2017 Dan Samuel, synthetic-reality.com, all rights reserved ; ; This particular starmap is an example of a DOTA-like game. It defines some basic things ; in the top-half, but most of the starmap is defined dynamically by the Lua script in the ; bottom half. Feel free to re-use parts of it as a foundation for your own starmaps. ; ; For purpose of this document, a starmap describes a 'star system' 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 system'. ; ; 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 "Spawn" locations ; * Power Up spawn locations and periods ; * Gravity Objects (suns, planets, black holes) ; * Rectangular Zones with special properties ; * Map-wide Properties (numTeams, safeBullets, etc) ; ; Things your optional BOTTOM HALF Lua Scripting can control ; ; * dynamically modify most of the settings from the top half ; * object-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 a short player-friendly description of your map. e.g. "Sort of like skate boarding with rockets" ; author What you, the author would like to be called. e.g. "Bill and Dave of SpaceX" ; 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. I don't care. I just use maproot ; 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 ; ; Note that the full name of a map's "persistent data" will be the maproot bound to the auther sernum, so two authors can use the same ; campaign name without any conflict (will not share data) since they have different sernums. This also allows the data to ; be shared over a version boundary, so players don't all have to start from scratch if you update your map someday. Unless you ; lose your sernum.... (uninstall the app to lose your sernum) ; [credits] mapname = Defense of the Clones maproot = samsynDOTC0 mapdesc = DOTA-like team based play author = Samsyn sernum = 1 version = 1.001 email = dan@synthetic-reality.com info = You should be able to mod this to make similar experiences with low effort tags = DOTC ;--------------- ; 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, 40 ;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 ; ; NOTE: TeamIds are not the same as ShipIds, in general, and colors track ; with ships, not teams. So be careful ; ; Colors are defined as three decimal numbers (0-255) in the order Red, Green, Blue. ; ; red grn blu [colors] 0 = 255, 204, 0 ; gold 8 = 204, 51, 104 ; WEST bot reddish (west team is 9!) 9 = 53, 103, 255 ; EAST bot blueish (east team is 10!) ; ;----------------- ; 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. ; ; 2017 new trick: Circular Zones. ; Use bottom = -1 to indicate you want a circle centered on (left, top) of radius 'right' ; ; 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 nothing rendered ; 1-63 use matching starmap color ; +100 outline ; +200 fill ; +300 both ; ; 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 (painful to all sides) ; 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 ; ; on this map, home zone heals you (get near your flag) ; ; ; st left top rigt btm tex team pain bul wp fr cx cy gp [zones] ;1 = 2, 3000,3000,5000,5000, 1, 0, 2, 0, 0, 0, 0, 0 ;9 = 2, 1948,1948,2148,2148, 1, 9, -20, 3, 0, 0, 0, 0 ;10 = 2, 6044,6044,6244,6244, 1, 10, -20, 3, 0, 0, 0, 0 11 = 2, 4000,4000,1000,-1, 0, 0, 2, 0, 0, 0, 0, 0 ; big round hurtful invisible zone around star 12 = 2, 2048,2048,100,-1, 308, 9, -20, 3, 0, 0, 0, 0 ; small healing zones around flags 13 = 2, 6144,6144,100,-1, 309, 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 ; these property names ; ; --------- ; Property: NumTeams ; ; 0 - no teams (every man for himself) ; 1 - fully human coop (can't hurt other players) ; 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. ; ; -------- ; Property: BulletPercent ; ; Do bullets live the normal duration? (travel the normal distance) ; ; Value (0-100) ; 0 ; This would result in no time at all, probably useless ; 50 ; no, they would last about half as long (50 percent) ; 100 ; Yes, exactly the normal distance ; ; [props] NumTeams = 2 SafeWeapons = 1 ; team melee is kinda hard in unsafe mode. ShowHandbook = 0 BulletPercent = 33 ; I need something closer to melee distance ;------------------------------------------------------------------------------ ; 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) -- FUNCTIONS I THINK MIGHT BE BAD IN THE API, FIX THEM HERE AND MOVE THEM THERE -- when registering a new bot, check if it is already in the array, and replace it function newBot( base, new ) -- make the new bot local newBotData = newBaseBot( base, new ) -- see if it is already in the list (NOW you see why ids must be unique!) local i for i=1,glNumBots do if( botList[ i ] and botList[ i ].id == newBotData.id ) then -- yeah, let's not make duplicates, so overwrite this one botList[ i ] = newBotData return newBotData end end -- I guess it's new, allocate a new slot -- inc first glNumBots = glNumBots + 1 botList[ glNumBots ] = newBotData return botList[ glNumBots ] end ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- -- 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. --================================================================================= -- Here are some directly declared assets -- this is the FACE bitmap to use for all towers. Everything inside the quotes must be just right. -- Use "options/nerd/export settings" to get this data for your currently selected face dataFaceTower = "DATA: ____00000000_______0557556560____00000000000000_0253555555552320023$WW$55$WW$330023$WW$55$WW$2100223355555552310_00000000000000__05525522255520__00535333222200_02302222222101200_220000000022_00__2121111112__0_0__22222222__0___0__275212__0_____0000000000___" ----------------- -- DOTA-like GAME ----------------- -- We make this a scene, so it automatically is sent copies of all messages for which -- it has registered a handler. The host of the session is the moderator in this case, -- as it usually will be, probably. -- The game consists of two teams, each with a home zone (SW and NE corners) with barriers -- constraining travel to three paths (hi, low and middle, if you like). Each home zone -- has a clone factory which can be destroyed by weapons fire. If your clone factory is -- destroyed, it is game over. -- The clone factories themselves can shoot back, plus they make minions at regular -- intervals, who then attack the enemy CF. -- the three paths are blocked at intervals by gates, each protected by two towers. -- destroying both towers opens the associated gate. -- you can pass right through friendly gates. -- we predeclare our npc indices local glNpcIndexFirstBot = 8 -- npcs 0-7 reserved for human players local glNpcIndexWestCF = 8 -- This is the West Team's Clone Factory (game over when dead) local glNpcIndexEastCF = 9 -- This is the East Team's Clone Factory (game over when dead) local glNpcIndexFirstMinion = 12 -- npcs 12 - 39 are the rotating, reusable minion NPCs local glTowerNpcIndexBase = 40 -- npcs 40 - 59 are gate defense towers, 2 per gate, 10 gates local glMaxMinions = glTowerNpcIndexBase - glNpcIndexFirstMinion -- currently 28, or 14 per team, which is hopefully a lot. local glNextMinionSubIndex = 0 -- start here and wrap if needed local NumBeatsToBuildAMinion = 10 -- one minion this often (or maybe N at a time) --local botWestCF = {} --local botEastCF = {} local TeamWestColor = RGB( 255, 0, 0 ) -- red versus blue local TeamEastColor = RGB( 64, 64, 255 ) local glWasPlayingDOTA = 0 -- goes to one after your first death -------------- -- THE CAST -------------- -- This drama requires the following bots: -- ships 0-7: reserved for human players (2 teams of four, WEST-RED and EAST-BLUE) -- the WEST and EAST factories themselves (Killer) -- 10 GATEs (barriers that can open and close) -- 20 TOWERS (2 defending each gate) (Killer) -- 28 MINIONS (dynamically allocated to both teams equally) (Hunter Killer) -- The actual NPC indices used for each is declared in the globals above. -- We only compete with other users in this same starmap, so we only have -- to stay a little bit organized. -- we maintain a large number of minion bots, that we precreate here and then spawn as needed -- here we will just keep references to them, but the botList is the definitiev list for message -- passing purposes local glMinionArray = {} -- we will rotate through this array of bot tables, let lua work out when to release stuff ---------- -- base class for all towers. Doesn't register for messages. Later we will create the actual towers, -- based on this base class, but with unique ids and such. botTower = newBaseBot( botRoot, { id = "tower", ship = glTowerNpcIndexBase, spawnZ = 0, -- in the plane of the system brain = nil, -- will not have a coroutine, just uses engine AI radarColor = 0, -- 0 means - do not show on radar senseRadius = 700, -- cannot SEE you past this canHitFirst = 1, -- really, this is to enable auto-target selection leadPercent = 75, -- imperfect shots wpnTgt = -1, -- they seem to be born shooting at remembered phantoms navTgt = -1, -- I suspect their AI still remembers their old location faceRadius = 30, -- render the bot's face, as a bitmap in space of this radius faceZ = 50, -- let it float a little, WoS-style shadowRadius = 40, -- render an oval shadow on top of barriers, but beneath ship shells shadowColor = 200, -- filled with black, no outline (maybe darker colors get more opaque alpha) -- I need a way to specify a weapon Id (and that should include a range/power adjustment) pilotInfo = { rank = "Tower", -- n/a for this item which should have no pilot info page name = "Tower", -- face = dataFaceTower, -- Some sort of cool structure that looks good with a vector 'turret' shell = "SHIP_000", -- stock SHIP asset to use VERY IMPORANT, THIS IS THE TOWER APPEARANCE shell2 = "SHIP_001", -- Just an experiment for now podW = 0, -- starting pods set the overall power of the thing podS = 14, -- 14 is the total max. podE = 4, rating = 1000, -- starting rating won = 0, -- starting stats lost = 0, lang = 2, -- G = 0 ? declared language rating of bot. }, } ) -- here is a base class for a factory that makes something -- factory-specific methods should live here -- A factory basically manufactures and delivers new items, themselves bots probably -- but the factory functions should ideally not know that directly and just invoke -- some build function at appropriate spawn times. Meanwhile, the factory should ideally -- give some clue as to what it is building and when it will be done. -- On this map, however, we need to build minions. Each team has a minion factory -- which generates minions at a syncrhonized rate. (in fact, only the host is running the -- factory code, and reports new minions via packets sent to other players) -- again, this is the base class, where we define common properties and functions for -- all factories. botFactory = newBaseBot( botRoot, { id = "factory", ship = glNpcIndexWestCF, -- I will set this at time of creation radarColor = 0, -- 0 means - do not show on radar senseRadius = 600, -- longest range canHitFirst = 1, -- really, this is to enable auto-target selection leadPercent = 75, -- imperfect shots wpnTgt = -1, -- they seem to be born shooting at remembered phantoms navTgt = -1, -- I suspect their AI still remembers their old location faceRadius = 30, -- render the bot's face, as a bitmap in space of this radius faceZ = 50, -- let it float a little, WoS-style shadowRadius = 40, -- render an oval shadow on top of barriers, but beneath ship shells shadowColor = 200, -- filled with black, no outline (maybe darker colors get more opaque alpha) -- I need a way to specify a weapon Id (and that should include a range/power adjustment) -- on this map, Clone Factories appear as thumbs pilotInfo = { rank = "", -- n/a for this item which should have no pilot info page name = "FactoryRoot", -- face = "FACE_014", -- stock FACE asset to use never seen? maybe tower is manned... can send radio... shell = "SHIP_000", -- stock SHIP asset to use VERY IMPORANT, THIS IS THE TOWER APPEARANCE podW = 5, -- pretty long shot podS = 14, -- very shielded podE = 0, -- never moves (but it COULD... maybe use tractor beam to drag it) rating = 1000, -- starting rating won = 0, -- starting stats lost = 0, lang = 2, -- G = 0 ? declared language rating of bot. }, -- here is my factory data beatsUntilDone = 0, -- nothing under construction -- All factory-specific functions should be kept here handler = { ['onBEAT'] = function( bot, args ) --log( 1, "onBEAT called in factory " .. bot.id ) -- OK, if we are the one making something if( sceneDOTA and (sceneDOTA.gameState == 2)) then -- game is in progress, factories should be building away if( iAmSceneHost( bot ) ) then --log( 1, "onBEAT called (and I am host) in factory " .. bot.id ) if( bot.beatsUntilDone > 0 ) then --log( 1, "factory " .. bot.id .. " advancing build, steps: " .. bot.beatsUntilDone ) -- one step closer bot.beatsUntilDone = bot.beatsUntilDone - 1 if( bot.beatsUntilDone <= 0 ) then -- we're done, manufacture it! bot:buildComplete() end end --log( 1, "onBEAT was handled without crash, in factory " .. bot.id ) end end end, }, startBuild = function( bot, beatsRequired) -- log( 1, "factory " .. bot.id .. " starting build " ) bot.beatsUntilDone = beatsRequired end, buildCount = 0, -- count the output of this factory buildComplete = function( bot ) --log( 1, "factory " .. bot.id .. " build complete " ) -- it's time to spawn a new minion -- I guess you would override this to have different sorts of factories make different stuff -- but in our case, we want to spin up a new minion -- relative to our clone factory local x = bot.spawnX + ((glNextMinionSubIndex % 4) * 128) local y = bot.spawnY + ((glNextMinionSubIndex / 4) * 128) -- tell everyone via message (myself included) to spawn this minion if( sceneDOTA ) then -- note that the host allocates the minion subIndex local ix = glNextMinionSubIndex glNextMinionSubIndex = glNextMinionSubIndex + 1 glNextMinionSubIndex = glNextMinionSubIndex % glMaxMinions local mood = 0 local path = 0 -- 0 1 or possibly 2 (jungle) bot.buildCount = bot.buildCount + 1 if (bot.buildCount % 2) == 1 then path = 1 end log( 1, "buildComplete[".. bot.buildCount.. "] sending minionBorn for ix " .. ix .. ", path " .. path ) sceneDOTA:sendMapEvent( 'minionBorn', "ix=" .. ix .. "&x=" .. x .. "&y=" .. y .. "&team=" .. bot.team .. "&mood=" .. mood .. "&path=" .. path ) end -- and immediately start a new one building bot:startBuild( NumBeatsToBuildAMinion ) end, } ) -- here are my actual factories, as used on this map -- they are clone factories, and if they die it is game over for their team -- they create minions at regular intervals who attempt to reach and destroy -- the enemy clone factory. -- these guys do appear as thumbs, and on radar -- And in theory they can be thought to be 'manned' and chatter now and then -- I need to add support for inline properties (locally defined ship shells and faces) botWestCF = newBot( botFactory, { id = "westCF", ship = glNpcIndexWestCF, radarColor = glNpcIndexWestCF, -- 0 means - do not show on radar team = TeamWest, -- I need a way to specify a weapon Id (and that should include a range/power adjustment) pilotInfo = { rank = "", name = "WEST", -- face = "FACE_016", -- stock FACE asset to use never seen? maybe tower is manned... can send radio... shell = "SHIP_000", -- stock SHIP asset to use VERY IMPORANT, THIS IS THE TOWER APPEARANCE }, } ) botEastCF = newBot( botFactory, { id = "eastCF", ship = glNpcIndexEastCF, radarColor = glNpcIndexEastCF, -- 0 means - do not show on radar team = TeamEast, -- I need a way to specify a weapon Id (and that should include a range/power adjustment) -- I fear I have to override this all or nothing, and maybe that's mostly a good thing, -- still I bet it leads to errors pilotInfo = { rank = "", -- maybe rank should be 'status' for something like this: 'Damaged' name = "EAST", -- face = "FACE_016", -- stock FACE asset to use never seen? maybe tower is manned... can send radio... shell = "SHIP_000", -- stock SHIP asset to use VERY IMPORANT, THIS IS THE TOWER APPEARANCE }, } ) --------------- -- hints and quips local nextTipOfTheDayIndex = 0 tips = { "Each GATE is guarded by two TOWERs. You have to destroy both of them, before the gate will open. ", "Of course, that's ENEMY gates. Friendly gates will let you pass right through. ", "Be kinda silly to have your OWN gates shoot at you! It's bad enough just avoiding the ricochets! ", "There are fewer gates if you take the short cut... past the star itself! ", "It might be a burnt out husk, but it's still a force to be reckoned with. ", "You're kinda puny. No offense.. In comparison, I mean. That's all. ", "SMACK. That's you running into the star. The star didn't even notice. I'm just saying. ", "If you avoid the star path, there are two enemy gates between you and the enemy Clone Reactor Core. ", "Or four towers, is a better way to think about it. ", "Towers are actually pretty good shots. Better than the attack drones even. ", "You probably want to wait until the tower is involved with someone else, before getting close. ", "Oh, you probably noticed this star system has some sort of dampening effect on your bullets. ", "Your bullets expire sooner, so they don't go as far... So you have to get a lot closer to your enemy. " , "So close, in fact, that you are in range of their attack. I know. Sounds unfair doesn't it? ", "But buck up. You're a hero! That's what both Watte and 'Slack' have to say about you! ", "Smitty seems to hold a bit of a grudge though. Not sure what THAT's about. ", "Anyway, like the guy said, you can pick either side (West or East, Red or Blue) but your choice does reflect upon you. ", "I mean, like, maybe it affects your reputation, or something. ", "Nah, just kidding. ", "...", "Or am I? ", "...", "I am", } 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 ----------------- -- THE MAP ----------------- -- -- This is for two teams (east vs west) of four players each. Each team has a spawn -- location containing its Clone Factory. (these are at opposite corners of a closed -- star system -- no wraparound, because of barriers. Goal is to destroy enemy CF -- and protect your own. You must fight through gates and minions and then destroy -- the Factory core. -- It can also be played in 'tank' mode (where friction stops you any time you stop engines) -- The session moderator can start a new Battle at any time (interrupting the current one, even) -- and moderator-hood can pass from one player to another if needed (maybe). -- Players are moved to spawn location and held during countdown, weapons and engines frozen. -- Separating the two bases are a series of gates, with -- two stationary Towers per gate. You have to destroy both Towers to open that gate. -- Gates will shoot at one side or the other, and possibly both, when you get in range. -- If you die along the way, you respawn. Towers call for help when attacked, and minions join the conflict -- -- There are three paths between the clone factories (you spawn near your own CF) -- going through the center is more direct, but exposes you to unknowns (and powerups) -- -- minions spawn automatically and travel to points of conflict to assist their team -- but their main AI is to attack the enemy clone factory -- -- Typical map. In this case, this map is generated completely by the API instead -- of being defined in the top half of the starmap. I compute a set of points and -- then create barriers/gates connecting those points. -- -- 0-------------------1-------------------2-------------------3 -- | | | | [A] - destructible gate -- | 32 [A} 28 [B} 35 | # - point number -- | | | EAST | * - dangerous star -- | 26 4-------------------5 (CF) | (CF) - Clone Factory -- | | | 25 | {A] - gate friendly to WEST -- | | | | [A} - gate friendly to EAST -- | | | | -- 6---{C]---7---------8 9---[D}--10---[E}--11 -- | | | | -- | 29 | * | 31 | -- | | | | -- 12 --{F]--13---{G]--14 15--------16---[H}--17 -- | | | | -- | | | | -- | WEST | | | -- | (CF) 18------------------19 27 | -- | 24 | | | -- | 33 {I] 30 {J] 34 | -- | | | | -- 20------------------21------------------22------------------23 -- lines are fixed barriers (non destructible) -- Your home reactor is a healing point, or maybe there is no healing point. -- Script is multiplayer friendly, knock on wood, and survives change of mod -- in mid game. -- control points, as globals, why not local maxG = 8000 -- size of starmap, minus a bit for borders and cowardice local borderG = ((8192-maxG)/2) -- center it, since collisions need to avoid edge of starmap -- these are the only X/Y values that we actually use. We derived everything else from these gX0 = borderG + ( 0 * maxG) / 100 gX1 = borderG + ( 25 * maxG) / 100 gX2 = borderG + ( 33 * maxG) / 100 gX3 = borderG + ( 50 * maxG) / 100 gX4 = borderG + ( 66 * maxG) / 100 gX5 = borderG + ( 75 * maxG) / 100 gX6 = borderG + (100 * maxG) / 100 gY0 = borderG + ( 0 * maxG) / 100 gY1 = borderG + ( 25 * maxG) / 100 gY2 = borderG + ( 33 * maxG) / 100 gY3 = borderG + ( 50 * maxG) / 100 gY4 = borderG + ( 66 * maxG) / 100 gY5 = borderG + ( 75 * maxG) / 100 gY6 = borderG + (100 * maxG) / 100 myStoryStyle = 1 -- StarWars scrolling text ----------------- -- THE DOTA SCENE ----------------- -- I believe this starmap has only this one scene sceneDOTA = newScene( sceneRoot, { id = "DOTA", beatsUntilLaunch = 10, -- the launch countdown timer beatsUntilSendState = 20, -- moderator sends state when this hits 0 incomingStateString = "", -- most recent state string from moderator -- gets SET in 'state' handler -- gets PROCESSED in playDOTA coroutine -- SCENE STATE -- state is updated only by moderator and is often expected to be processed sequentially -- for attractive animations. gameState = 0, -- 0 between games -- 1 move to start -- 2 begin play (reset Towers) -- 3 game over gameOver = 0, -- 0 during game, turns into TeamWest or TeamEast when game is over -- an array of 2D points we use to place barriers per the diagram above framePoints = { {gX0, gY6}, {gX2, gY6}, {gX4, gY6}, {gX6, gY6}, -- 0 1 2 3 {gX2, gY5}, {gX4, gY5}, -- 4 5 {gX0, gY4}, {gX1, gY4}, {gX2, gY4}, {gX4, gY4}, {gX5, gY4}, {gX6, gY4}, -- 6 7 8 9 10 11 {gX0, gY2}, {gX1, gY2}, {gX2, gY2}, {gX4, gY2}, {gX5, gY2}, {gX6, gY2}, -- 12 13 14 15 16 17 {gX2, gY1}, {gX4, gY1}, -- 18 19 {gX0, gY0}, {gX2, gY0}, {gX4, gY0}, {gX6, gY0}, -- 20 21 22 23 {(gX0 + gX2)/2, (gY0 + gY2)/2}, -- 24 is center of west home zone {(gX4 + gX6)/2, (gY4 + gY6)/2}, -- 25 is center of east home zone {gX1, gY5}, -- 26 is center of NW home zone (in 4 team mode) {gX5, gY1}, -- 27 is center of SE home zone {gX3, (gY5 + gY6)/2}, -- 28 center of top {(gX0 + gX1)/2, gY3}, -- 29 center of left {gX3, (gY0 + gY1)/2}, -- 30 center of bottom {(gX5 + gX6)/2, gY3}, -- 31 center of right {(gX0 + gX1)/2, (gY5 + gY6)/2}, -- 32 NW in line with gates {(gX0 + gX1)/2, (gY0 + gY1)/2}, -- 33 SW in line with gates {(gX5 + gX6)/2, (gY0 + gY1)/2}, -- 34 SE in line with gates {(gX5 + gX6)/2, (gY5 + gY6)/2}, -- 35 NE in line with gates }, -- triples defining where barriers go, and what type (0-normal, 1-hates west, 2 - hates east, 3 - hates both) -- { type, leftOrTopPoint, rightOrBottomPoint } barriers = { {0, 0, 1}, {0, 1, 2}, {0, 2, 3}, -- -- -- -- {0, 0, 6}, {1, 1, 4}, {1, 2, 5}, {0, 3,11}, -- | A B | {0, 4, 5}, -- -- {0, 4, 8}, {0, 5, 9}, -- | | {2, 6, 7}, {0, 7, 8}, {1, 9,10}, {1,10,11}, -- C -- D E {0, 6,12}, {0, 7,13}, {0,10,16}, {0,11,17}, -- | | | | {2,12,13}, {2,13,14}, {0,15,16}, {1,16,17}, -- F G -- H {0,12,20}, {0,14,18}, {0,15,19}, {0,17,23}, -- | | | | {0,18,19}, -- -- {2,18,21}, {2,19,22}, -- I J {0,20,21}, {0,21,22}, {0,22,23}, -- -- -- -- }, -- is used to remember which H/V barrier offset we used, and current state of gate -- state 0 (dead) 1 (alive) -- { gateIndex, H0/V1, barrierIndex, state } (barrierIndex will be written to as barrier index is assigned -- gateIndex is also used to get npcIndex = npcBase + 2 * gateIndex gates = { {0, 1, 0, 1}, {1, 1, 0, 1}, -- A B {2, 0, 0, 1}, {3, 0, 0, 1}, {4, 0, 0, 1}, -- C D E {5, 0, 0, 1}, {6, 0, 0, 1}, {7, 0, 0, 1}, -- F G H {8, 1, 0, 1}, {9, 1, 0, 1}, -- I J }, -- is used to remember state of individual towers (20 total, 2 per gate) -- A B C D E F G H I J towerState = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, -- 0 is dead, 1 is alive towerGateIx = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 }, -- each tower protects one gate ----------- -- onAWARE: i start this when they can first see the map, before they pick color -- but after they connect to server instance. -- Its goal is to inform/entertain the player and cajole them into picking a drone. onAwareCutScene = function( scene ) if( glWasPlayingDOTA == 0 ) then -- fresh launch, virgin map scene.state = 0 -- back to the beginning scene:resetMap() -- build the DOTA map (destroy the upper half map) end 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 setCamZoom( 3, 2000 ) -- back a little from the star to make it look small and burnt setCamOrbit( 5 ) -- gently orbit wait(3) -- Let there be Title announce('DEFENSE OF THE CLONES' ) wait( 5 ) nextTipOfTheDayIndex = 0 -- reset hints -- scroll the following text in fancy narration mode textStory( myStoryStyle, [[ / / Welcome to Omega Centauri, once a bright blue star, but now a burnt-out husk. / / Long ago, the forces behind droneNET chose to build two Clone Factories here... and never turned them off. / / The Factories themselves have achieved sentience, of a sort. The red one calls itself WEST and the blue one chose EAST. / / For reasons lost to time, the Factories decided to become enemies, and each yearns for the destruction of the other. / / Perhaps this was by design, to limit their power by providing a perfect counter balance. We will probably never know. / / Between them, they have completely fenced-in this star system, and blocked all traffic with heavily guarded gates. / / To build these barriers, towers and attack drones, the Factories drain both energy and mass from Omega. / / Locked in an eternal struggle they can never win, they have burned this star down to its core. / / Your mission is to put an end to this waste... by destroying one of the Factories. / / Pick a side, perhaps RED for the hot blood of humans, or BLUE for an exotic alien sang-froid! / / It doesn't matter which side you pick, but you must put an end to this... Balance. There must be a winner. / / Locked gates in your path can only be opened by the destruction of both their guard towers. / / This is our destiny, and our duty. Time is running out! Heed our call to battle! Join us! / / ]] ) wait (20) -- let it scroll off screen -- now the hints/quips 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: Here we either start a new DOTA game, or come back -- to life in one we already died in once. onLaunchCutScene = function( scene ) log(1," Starting onLaunch cut scene") -- I am pretty sure the spawn point is there, and I want the reset camera to -- center on spawn point respawnPlayer( 1 ) -- this should put the player on the map at official spawn point resetCamera() -- starts another big zoom in, I think log(1, "Back from respawnPlayer") -- might be nice to blink the options button (or is it the MENU button?) if ( glWasPlayingDOTA ~= 0 ) then -- resume game automatically, otherwise wait for moderator to start it scene:resumeDeadPlayer() else -- fresh game start -- There is some animation here as the barriers rebuild scene:resetMap() -- if he is coming back from the dead, this restarts all towers -- so I see towers come back that were dead wait( 3 ) --announce( "Use STARMAP OPTIONS To start game." ) scene:playDOTA() -- launch always ends up starting this coroutine, which then runs the battle statemachine end end, -- We were playing, and were up to date, and now we just relaunched, maybe in -- a different color even, so fully reset US, but try to use as much of the -- world state as possible, though new state is probably coming -- BUT IF I AM MOD, DO NOT RESTART THE GAME BY ACCIDENT resumeDeadPlayer = function( scene ) scene.resurrect = 1 scene:playDOTA( ) end, updateDisplays = function( scene ) -- MENU options -- this one is always there, but moderator only. we update since it can change mid-game option(2, 1, "uSTART_DOTA", "Re-start Defense of the Clones" ) -- MAIN DISPLAY STAT local x = 50 local y = 75 if( scene.gameState == 0 ) then -- waiting for start if iAmModerator() then -- put a button right in the moderator's face display( 0, x, y, "You are the moderator", "START BATTLE", "uSTART_DOTA" ) else -- let everyone else know who they are waiting on display( 0, x, y, "Waiting for Moderator to start battle", "" ) end elseif( scene.gameState == 1 ) then -- countdown display( 0, x, y, "Battle Starting Soon!", scene.beatsUntilLaunch ) elseif( scene.gameState == 2 ) then -- game in progress displayOff(0) elseif( scene.gameState == 3 ) then -- gameOver local winner = "WEST" local winningTeam = 0+scene.gameOver if winningTeam == TeamEast then winner = "EAST" end if myShipTeam == winningTeam then display( 0, x, y, "You and " .. winner .. " have won!", "GAME OVER" ) else display( 0, x, y, winner .. " et al have beaten you!", "GAME OVER" ) end else displayOff(0) end end, -- this is our main DOTA coroutine, it is run by all players, but -- only the current moderator controls state changes -- so it all just started. I need to -- stop anything in progress -- reset game state for a new game -- tell all the clients to do the same -- that gets everyone to their startig positions and starts the countdown -- then I start scanning for game over criteria -- and reacting to incoming commands from the moderator (myself in this case) -- I announce game over to everyone -- non moderators do much the same thing, but generally only receive state -- and do not need to send any state on their own, since ships send already what is needed. playDOTA = function( scene ) log(1, "playDOTA invoked " ) local playing = 1 -- while forever basically while playing do scene.beatsUntilSendState = 10 -- postpone/schedule state sending local loopBeats = 3 -- our responsiveness basically -- everyone drops into working world at start now, as soon as they launch -- and the world works (updateMap), but will get reset once the moderator -- starts the battle while( scene.gameState == 0 ) do scene:updateMap( ) -- if I need this to be slow, I should do it inside updateMap() wait( loopBeats ) end -- we now assume we are in state 1, the countdown to battle -- we reset the map and freeze everyone in starting locations scene.gameOver = 0 -- when game ends, this gets set to TeamEast or TeamWest (the winner) setCamZoom( CamZoomWide, 1000 ) wait( 2 ) announce( 'Defend your Clone Factory!' ) wait( 1 ) makeRepairs( myShipIndex, REPAIR_MASK_ALL ) freezePlayer() wait( 2 ) resetCamera( 1 ) if ( scene.resurrect == 0 ) then scene.gameState = 1 -- we are just getting started scene.beatsUntilLaunch = 10 scene:resetMap() -- everyone comes through this path once else -- we need to make sure the towers have proper teamIds in case player switched sides -- scene:resetMap() -- this needs to not alter tower alive state, but do everything else -- and now we should wait for, or ask for, real state end glWasPlayingDOTA = 1 -- ok, so NOW if we die, we don't reset the map -- respawn player log(1, "Spawning Player on DOTA" ) respawnPlayer( 1 ) -- back to map start point, re-do map zoom from space scene.resurrect = 0 -- back to the game, though maybe this doesnt matter -- newcomer should wait here for state from moderator, if mid-game while( scene.beatsUntilLaunch > 0 ) do --scene:updateMap( ) -- this might be the one time to suppress this wait(1) scene.beatsUntilLaunch = scene.beatsUntilLaunch - 1 end log(1, "Releasing player from freeze" ) releasePlayer() -- todo: defer for countdown scene.gameState = 2 -- means we are actively playing -- now we mainly want to react to incoming messages from the mod log(1, "Entering main game loop" ) while ( scene.gameOver == 0 ) do scene:updateMap( ) -- clients also do some updating wait( loopBeats ) end -- game must be over now if iAmSceneHost( scene ) then log(1, "Game over, I am telling all players winning team " .. scene.gameOver ) scene:sendMapEvent( "gameOver", "team=" .. scene.gameOver ) scene.gameState = 3 -- I need to tell myself a little early end -- now continue simulation until someone clears this state while( scene.gameState == 3 ) do scene:updateMap( ) wait( loopBeats ) end wait(4) -- superstition? -- and now continue our infinite loop log(1, "playDOTA complete, looping back to state 0 " ) scene.gameState = 0 end end, -- whip up a fresh batch of bots, but they have not been spawned yet, just assigned all their unique ids resetMinions = function( scene ) log( 1, "-- resetMinions --" ) local i for i=0,(glMaxMinions-1) do -- remove any old npc bound to this slot if ( glMinionArray[ i+1 ] ~= nil ) then glMinionArray[ i+1 ]:exit() glMinionArray[ i+1 ] = nil end -- add a new one.. note this is code, but we include an inline defined table as an argument glMinionArray[ i+1 ] = newBot( botRoot, { id = "minion"..i, ship = glNpcIndexFirstMinion+i, radarColor = 0, -- 0 means - do not show on radar senseRadius = 500, -- or do I set this again at crearuib -- I need a way to specify a weapon Id (and that should include a range/power adjustment) -- on this map, Clone Factories appear as thumbs pilotInfo = { rank = "", -- n/a for this item which should have no pilot info page name = "Attack Drone "..i, -- face = "FACE_014", -- stock FACE asset to use never seen? maybe tower is manned... can send radio... shell = "SHIP_000", -- stock SHIP asset to use VERY IMPORANT, THIS IS THE TOWER APPEARANCE podW = 0, -- starting pods set the overall power of the thing podS = 22, -- fairly hard to kill podE = 0, -- fairly slow rating = 1000, -- starting rating won = 0, -- starting stats lost = 0, lang = 2, -- G = 0 ? declared language rating of bot. }, } ) -- If I just said handler = { new handler stuff } it overwrites the existing handler ref, so -- I would lose previously inherited handlers. By directly assigning it, after object creation, -- it doesn't lose the existing handlers. There is probably a lua syntax for what I want, but -- I don't know it yet :-) -- add a 'waypoint' message handler. If we have more waypoints, head to the next one. glMinionArray[ i+1 ].handler[ 'waypoint' ] = function( bot, args ) local ix = 0 + args.ix -- the ship who reached their waypoint if( ix == bot.ship ) then -- I guess it's ME! I guess *I* reached a waypoint! --log( 1, "Bot " .. bot.id .. " recd waypoint message " ) handleWayPoint( bot ) end end -- this should only be sent to a unique bot id glMinionArray[ i+1 ].handler[ 'newWayPoint' ] = function( bot, args ) local ix = 0 + args.ix -- the new waypoint index local hisId = args.id if( ix and bot.numWayPts and bot.numWayPts > ix and hisId == bot.id ) then bot.nextWayPt = ix log( 1, "Bot " .. bot.id .. " recd newWaypoint="..ix.." from " .. hisId ) handleNewWayPoint( bot ) end end end end, -- this actually spawns the ship npc for a minion you already created a table for -- ix is relative to glNpcIndexFirstMinion addMinion = function( scene, ix, x, y, team, mood, path ) -- make the bot from the same team, and such, --log( 1, "addMinion " .. ix .. " at (" .. x .. ", " .. y .. ") team " .. team .. ", mood " .. mood .. ", path " .. path ) local minion = glMinionArray[ ix + 1 ] if( minion == nil ) then else -- looks cool, spawn it with suitable brain and location minion.canHitFirst = 1 -- really, this is to enable auto-target selection minion.senseRadius = 500 -- center of gate should not sneak past minion.leadPercent = 75 -- imperfect shots minion.wpnTgt = -1 -- they seem to be born shooting at remembered phantoms minion.navTgt = -1 -- I suspect their AI still remembers their old location minion.team = 0 + team minion:enter( mood, -1, x, y ); -- calls createNPC() for us -- force the color local color = TeamWestColor if( minion.team == TeamEast ) then color = TeamEastColor end setMapColor( minion.ship, color ) -- start off stationary local vx = 0 local vy = 0 setShipVel( minion.ship, vx, vy ) -- now set my waypoints local fp = scene.framePoints if minion.team == TeamWest then -- west to east, taking all three paths -- left/top path if path == 0 then -- upper setWayPoints( minion, 5, { fp[33+1], fp[29+1], fp[32+1], fp[28+1], fp[35+1] } ) else -- lower setWayPoints( minion, 5, { fp[33+1], fp[30+1], fp[34+1], fp[31+1], fp[35+1] } ) end else -- east to west, taking all three paths -- top/left path (should bump into left/top path at corner if path == 0 then -- upper setWayPoints( minion, 5, { fp[35+1], fp[28+1], fp[32+1], fp[29+1], fp[33+1] } ) else -- lower setWayPoints( minion, 5, { fp[35+1], fp[31+1], fp[34+1], fp[30+1], fp[33+1] } ) end end end end, -- we have two clone factories, located in the home zone of each team resetFactories = function( scene ) log( 1, "-- resetFactories --" ) local fp = scene.framePoints[ 24+1 ] local x = fp[1] local y = fp[2] -- we hold still, but can shoot at stuff local mood = BEHAVE_MASK_KILL botWestCF.spawnX = x botWestCF.spawnY = y botWestCF.team = TeamWest botWestCF:exit() -- in case it was in use botWestCF:enter( mood, -1, x, y ) fp = scene.framePoints[ 25+1 ] x = fp[1] y = fp[2] botEastCF.spawnX = x botEastCF.spawnY = y botEastCF.team = TeamEast botEastCF:exit() -- in case it was in use botEastCF:enter( mood, -1, x, y ) -- override their colors setMapColor( botWestCF.ship, TeamWestColor ) setMapColor( botEastCF.ship, TeamEastColor ) -- make sure they hold still local vx = 0 local vy = 0 setShipVel( botWestCF.ship, vx, vy ) setShipVel( botEastCF.ship, vx, vy ) -- start the factories botWestCF:startBuild( NumBeatsToBuildAMinion ) botEastCF:startBuild( NumBeatsToBuildAMinion ) end, -- allocate 2 gates per gate barrier, and spawn and program the NPC towers at each end resetGates = function( scene ) log( 1, "-- resetGates --" ) local gateIndex for gateIndex = 1, 10 do local gate = scene.gates[ gateIndex ] -- reset the state to alive scene:resetGate( gate, 1 ) end end, -- this actually opens (newState 1) and closes (newState 0) the gate, -- but doesn't deal with the towers resetGate = function( scene, gate, newState ) -- get the info for this gate local gx = 0 + gate[ 1 ] local isV = 0 + gate[ 2 ] local barrierIx = 0 + gate[ 3 ] local gState = 0 + gate[ 4 ] -- maybe log something here --log( 1, "Reset GateIx: " .. gx .. ", bIx: " .. barrierIx .. ", newState: " .. newState ) -- switch to new state (0 - broken/open, 1 - working/closed) gState = newState gate[ 4 ] = gState -- and start barrier animation local barrierState = 2 -- assume we will animate open (state 0) if( newState == 1 ) then barrierState = 3 -- we will animate closed instead (state 1) end -- reset the barrier state if( isV == 1 ) then setMapBarrierStateV( barrierIx, barrierState ) else setMapBarrierStateH( barrierIx, barrierState ) end end, openGate = function ( scene, gateIx ) scene:resetGate( scene.gates[ gateIx + 1 ], 0 ) end, closeGate = function ( scene, gateIx ) scene:resetGate( scene.gates[ gateIx + 1 ], 1 ) end, resetTower = function( scene, npcIndex, fpa, towerState, towerTeamId ) -- basically just spawn a tower NPC at this location with the appropriate -- AI mode. -- log( 1, "resetTower for npcIndex " .. npcIndex .. ", team " .. towerTeamId ) local tgtIx = -1 -- this will come from aggro local toX = math.floor( fpa[ 1 ] ) local toY = math.floor( fpa[ 2 ] ) local behaveMask = BEHAVE_MASK_KILL -- but towers always want to kill something -- we use this same bot table for all towers, so just for init botTower.id = "tower" .. npcIndex botTower.ship = npcIndex botTower.spawnX = toX botTower.spawnY = toY botTower.team = towerTeamId botTower.canHitFirst = 1 -- really, this is to enable auto-target selection botTower.senseRadius = 500 -- if you get this close botTower.leadPercent = 100 -- perfect shots log( 1, "resetTower for npcIndex " .. npcIndex .. " at (" .. toX .. ", " .. toY .. ") team " .. towerTeamId ) botTower:exit() -- in case it was already there, we want a shiny new one botTower:enter( behaveMask, tgtIx, toX, toY ) --log( 1, "resetTower for npcIndex " .. npcIndex .. " back from bot:enter() " ) -- the official state local towerIx = npcIndex - glTowerNpcIndexBase scene.towerState[ towerIx + 1 ] = towerState -- this tower is in business -- set the ship color to the appropriate tower color -- we also need to pick and set the team affiliation (west east or none) local towerColor = TeamWestColor if ( TeamEast == towerTeamId ) then towerColor = TeamEastColor end setMapColor( npcIndex, towerColor ) end, nextHBarrier = 0, -- I will just allocate these in order nextVBarrier = 0, buildDOTAFrame = function( scene ) -- we take over scene.nextHBarrier = 0 scene.nextVBarrier = 0 local gateIx = 0 --for each in barriers for index,value in ipairs( scene.barriers ) do -- value should be a table (triplet) --log( 1, "buildDOTAFrame barrier index ".. index ) local type = value[ 1 ] local pt1 = value[ 2 ] local pt2 = value[ 3 ] log( 1, "buildDOTAFrame barrier index ".. index .. ", type: " .. type .. ", pt1: " .. pt1 .. ", pt2: " .. pt2 ) local newGateState = 0 -- not sure what this is yet. might need towerState instead? local barrierIndex = scene:addBarrierOrGate( type, pt1, pt2, gateIx ) if( barrierIndex >= 0 ) then -- we just added a gate, and we add them in order --log( 1, "buildDOTAFrame back from addBarrierOrGate. barrier index " .. index .. ", bIx: " .. barrierIndex ) local gate = scene.gates[ gateIx + 1 ] if( gate ) then gate[3] = barrierIndex -- remember it, so we can turn it off later gate[4] = newGateState end gateIx = gateIx + 1 --wait(1) end end wait(1) -- now the spawn locations -- These are the centers of our clone factories local fpW = scene.framePoints[24 + 1] local fpE = scene.framePoints[25 + 1] -- place our spawn points around our factories local i = 0 for i=1,8,1 do local ix = i - 1 local hdg = 0 local x = fpW[1] local y = fpW[2] if( (i % 2) == 0 ) then -- make that east x = fpE[1] y = fpE[2] hdg=180 end -- now add a y/x offsetset from the CF itself in the center local offX = maxG/50 local offY = maxG/50 -- now adjust their signs if( 0 ~= bit32.band( i, 2 ) ) then offX = -offX end if( 0 ~= bit32.band( i, 4 )) then offY = -offY end x = x + offX y = y + offY log( 1, "buildDOTAFrame setMapSpawn ix: ".. ix .. ", x: " .. x .. ", y: " .. y .. ", hdg: " .. hdg ) setMapSpawn( ix, x, y, hdg ) end end, -- create a new point between the ones given tweenPoint = function( fp1, fp2, percent ) local bottom = 0 + fp2[2] local top = 0 + fp1[2] local left = 0 + fp1[1] local right = 0 + fp2[1] local dx = right - left local dy = top - bottom local x = left + (1 * dx)/3 local y = bottom + (2 * dy)/3 return { x, y } end, ------------- -- Add an object to the map, that connects two base-0 point indices. -- type: 0 (barrier), 1 (gate, hates west), 2 (gate, hates east), 3 (gate, hates both) addBarrierOrGate = function( scene, type, pt1, pt2, gateIx ) -- work out if vertical (x values match), and remember lua needs +1 for index here local fp1 = scene.framePoints[ pt1+1 ] local fp2 = scene.framePoints[ pt2+1 ] local isVertical = 0 if ( fp1[ 1 ] == fp2[ 1 ] ) then isVertical = 1 -- x values match end local barrierIndex = -1 if( type == 0 ) then -- no gate, just a barrier scene:addWall( isVertical, type, fp1, fp2 ) else -- yes gate, which means 3 barriers in a row, where the -- middle one is the actual gate (can open/close) local bottom = 0 + fp2[2] local top = 0 + fp1[2] local left = 0 + fp1[1] local right = 0 + fp2[1] local dx = right - left local dy = top - bottom local fpa = { left + (1 * dx)/3, bottom + (2 * dy)/3 } local fpb = { left + (2 * dx)/3, bottom + (1 * dy)/3 } --log(1, "GATE(".. fp1[1] .. ", " .. fp1[2] .. ") -> (" -- .. fpa[1] .. ", " .. fpa[2] .. ") -> (" -- .. fpb[1] .. ", " .. fpb[2] .. ") -> (" -- .. fp2[1] .. ", " .. fp2[2] .. ")" -- ) -- add the three barriers, remember the barrier index of the 'gate' scene:addWall( isVertical, 0, fp1, fpa ) barrierIndex = scene:addWall( isVertical, type, fpa, fpb ) scene:addWall( isVertical, 0, fpb, fp2 ) -- and while we're at it, here we make towers while we know where they go local npcIndex = (0 + glTowerNpcIndexBase) + (gateIx * 2) -- 2 per gate -- work out a team designation for the AI local towerTeamId = TeamNone if( type == 1 ) then -- hates west towerTeamId = TeamEast elseif( type == 2 ) then -- hates east towerTeamId = TeamWest end log( 1, "addBarrierOrGate adding gateIx " .. gateIx .. ", tower NPCs at index " .. npcIndex ) scene:resetTower( npcIndex, fpa, 1, towerTeamId ) scene:resetTower( npcIndex+1, fpb, 1, towerTeamId ) --log( 1, "addBarrierOrGate done resetting towers for gateIx " .. gateIx .. ", bIx:" .. barrierIndex ) end return barrierIndex end, addWall = function( scene, isVertical, type, fp1, fp2 ) local state = 3 -- so they animate into existence local bottom = math.floor( 0 + fp2[2] ) -- these must be ints local top = math.floor( 0 + fp1[2] ) local left = math.floor( 0 + fp1[1] ) local right = math.floor( 0 + fp2[1] ) local xpar = 0 -- work this out from type (team) -- 0 - bouncy -- 9 - weat -- 10 - east local color = 0 -- default local pain = 0 -- default if( type > 0 ) then color = type -- mainly for debug end if( type == 1 ) then -- hates west xpar = 10 color = 2 -- bluish elseif( type == 2 ) then -- hates east xpar = 9 color = 1 -- redish elseif( type == 3 ) then -- hates both xpar = 0 color = 3 -- orangish end local barrierIndex = scene.nextHBarrier if( isVertical == 1 ) then barrierIndex = scene.nextVBarrier setMapBarrierV( barrierIndex, state, bottom, top, left, color, xpar, pain ) scene.nextVBarrier = scene.nextVBarrier + 1 else setMapBarrierH( barrierIndex, state, left, right, top, color, xpar, pain ) scene.nextHBarrier = scene.nextHBarrier + 1 end return barrierIndex end, ------------------------------ -- optional message handlers (FOR THIS SCENE ONLY -- cool!) handler = { -- Moderator requests new DOTA session -- only the moderator sees this ['uSTART_DOTA'] = function( scene, args ) log( 1, "uSTART_DOTA handler in scene id ".. scene.id .. " was called" ) scene.gameState = 1 -- I cheat to make the button go away right away. scene:sendMapEvent( "startBattle" ) -- command everyone (myself included) to set up and start coroutine end, -- Periodic Timer ['onBEAT'] = function( scene, args ) --log( 1, "onBEAT handler in scene id ".. scene.id .. " was called" ) scene:updateDisplays() -- keep UI up to date if scene.beatsUntilSendState > 0 then --log(1, "onBEAT beatsUntilSendState " .. scene.beatsUntilSendState ) scene.beatsUntilSendState = scene.beatsUntilSendState - 1 end end, -- messages we expect to hear only from the moderator -- the moderator is telling us all to start a new session. -- we should already be in the coroutine, so just advance gamestate ['startBattle'] = function( scene, args ) log( 1, "startBattle handler in scene id ".. scene.id .. " recd from ser:" .. args.sernum .. "/ mod:" .. args.fromMod ) if( args.fromMod == 1 ) then glWasPlayingDOTA = 0 -- this means a fresh launch of a new session scene.resurrect = 0 -- and it's our start scene.gameState = 1 -- starts the countdown end end, ['towerDead'] = function( scene, args ) local towerIx = args.towerIx scene.towerState[ towerIx + 1 ] = 0 botTower.id = "tower" .. (glTowerNpcIndexBase + towerIx) log( 1, "towerDead/scene " .. scene.id .. ", tower " .. towerIx .. ": " .. botTower.id ) destroyNpc( botTower ) end, ['minionBorn'] = function( scene, args ) log( 1, "on minionBorn in scene " .. scene.id .. " with ix " .. args.ix .. ", path " .. args.path ) scene:addMinion( 0+args.ix, 0+args.x, 0+args.y, 0+args.team, 0+args.mood, 0+args.path ) end, ['minionDead'] = function( scene, args ) local ix = args.minionIx if ix then local minion = glMinionArray[ ix + 1 ] if ( minion ) then log( 1, "minionDead/scene " .. scene.id .. ", minion " .. ix .. ": " .. minion.id ) destroyNpc( minion ) end end end, ['state'] = function( scene, args ) local stateString = args.state if( iAmModerator() ) then else log( 1, "incoming state: " .. stateString ) scene.incomingStateString = stateString end end, -- these generally set booleans which are sensed later ['onDAMAGE'] = function( scene, args ) --log( 1, "onDAMAGE handler in ".. scene.id .. " was called for ship " .. args.ship .. " by " -- .. args.attacker .. " pain: " .. args.damage .. " energyLeft: " .. args.energy ) -- I, the scene, handle the gate and minion AI in this regard. I am mainly -- thinking here about -- running away and seeking a recharge -- saying something appropriate if my bot registered a personality end, ['onDEAD'] = function( scene, args ) log( 1, "onDEAD handler in ".. scene.id .. " was called for ship " .. args.ship .. ": " .. args.name .. " by " .. args.killer .. ": " .. args.killerName ) -- I, the scene, handle the game over logic -- If I am the moderator -- I should be the one to check if a tower died and if it is now an open gate -- and if so, I send a gate state packet to everyone (including myself) if ( iAmModerator() ) then scene:handleDeadPacket( 0+args.ship, args.name, 0+args.killer, args.killerName ) end end, ['gameOver'] = function(scene, args) local team = 0 + args.team log(1, "on gameOver handler in scene " .. scene.id .. " winning team: " .. team ) scene:announceGameOver( team ) end, }, -- end of handler table -- get everything ready, move my player to the spawn point, and start the countdown -- freeze my player at the spawn point resetMap = function( scene ) -- clear old stuff (barriers and zones) clearBarriers( 2 ) wait(3) -- am I in a coroutine? -- set new spawn points -- set new barriers scene:buildDOTAFrame() -- add gates scene:resetGates() -- prep the minions array (no spawned NPC yet, but full bot definitions) scene:resetMinions(); -- add the clone factories scene:resetFactories(); -- move my player to spawn point -- end, -- send a message from the mod, to all players, even the mod -- the mod should only change state in reaction to one of these -- (which allows them to be queued and processed in order for animation purposes) sendMapEvent = function( scene, event, args ) if( iAmSceneHost( scene ) ) then -- send it to this scene, on all machines local args2 = "echo=1" if( args ) then args2 = args2 .. "&" .. args end sendToObj( scene.id, event, args2 ) end end, -- this is called on all players machines announceGameOver = function( scene, winningTeam ) scene.gameState = 3 -- game over (changes overlay display) scene.gameOver = winningTeam --announce( "Game OVER" ) log(1,"announceGameOver winningteam " .. winningTeam ) if( winningTeam == TeamWest ) then announce( "Factory WEST exults in victory!" ); else announce( "Factory EAST exults in victory!" ); end -- I suspect I could/should use gameState for this now glWasPlayingDOTA = 0 -- next time you launch, it's a new game end, -- only moderator does this handleDeadPacket = function( scene, ship, name, killer, killerName ) -- check for dead towers if( (ship >= glTowerNpcIndexBase) and (ship < (glTowerNpcIndexBase + 20)) ) then -- it was a tower local towerIx = (ship - glTowerNpcIndexBase) -- send a notice to all, that it is dead log(1, "moderator issuing towerDead, ix " .. towerIx ) scene:sendMapEvent( 'towerDead', "towerIx=" .. towerIx ) end -- we also check for dead minions if( (ship >= glNpcIndexFirstMinion) and (ship < glNpcIndexFirstMinion + glMaxMinions) ) then -- it was a minion local minionIx = (ship - glNpcIndexFirstMinion) -- send a notice to all, that it is dead log(1, "moderator issuing minionDead, ix " .. minionIx ) scene:sendMapEvent( 'minionDead', "minionIx=" .. minionIx ) end -- and we check for dead clone factories -- only the mod does this, but will cause a gameover message to be sent if( botEastCF and ship == botEastCF.ship ) then log(1, "east is dead, west wins") scene.gameOver = 0 + TeamWest end if( botWestCF and ship == botWestCF.ship ) then log(1, "west is dead, east wins") scene.gameOver = 0 + TeamEast end end, -- return s number of living towers that support this gate gateSupport = function( scene, gateIx ) local count = 0 local towerIx = 0 for towerIx=0,19 do if scene.towerState[ towerIx + 1 ] == 1 then -- this tower is alive if( scene.towerGateIx[ towerIx + 1 ] == gateIx) then -- and it supports this gate count = count + 1 end end end return count end, -- towerIx is 0 based towerIsAlive = function( scene, towerIx ) if( scene.towerState[ towerIx + 1 ] ) then return scene.towerState[ towerIx + 1 ] == 1 end return nil end, -- ix is 0 based, within minions minionIsAlive = function(scene, ix ) local minion = glMinionArray[ ix + 1 ] if ( minion ) then return botIsAlive( minion ) else return nil -- false end end, -- all run this, so just send state as needed -- this is for time varying things, and reacting to booleans -- set by onXXX messages. -- but, basically, this is what keeps the map alive, and the rules working -- so you want to call this in all game states, if you want to be able to -- blow up towers and open gates between battles. updateMap = function( scene ) local iAmMod = iAmSceneHost( scene ) -- can change mid-game -- update gate states local gateIx = 0 for gateIx = 0,9 do --log(1, "about to count supporting tower for gate " .. gateIx ) local gate = scene.gates[ gateIx + 1 ] local count = scene:gateSupport( gateIx ) --log( 1, "updateMap gateIx " .. gateIx .. ", supported by " .. count .. " towers" ) if( 0 == count ) then -- this gate has no support, so it is 'open' if gate[4] == 1 then log(1,"Opening gate " .. gateIx) scene:openGate( gateIx ) end else -- this gate is closed --log(1, "thinking about closing gate " .. gateIx ) if gate[4] == 0 then log(1,"Closing gate " .. gateIx) scene:closeGate( gateIx ) end end end -- update minions maybe -- update factory state -- check for game over -- periodically just send a full status update if( iAmMod ) then --log(1, "I AM MODERATOR beats " .. scene.beatsUntilSendState) else --log(1, "I AM NOT MODERATOR beats " .. scene.beatsUntilSendState) end if( iAmMod ) then -- I am the moderator, I periodically emit a full state packet if( scene.beatsUntilSendState and (scene.beatsUntilSendState == 0) ) then -- log(1, "SUMMARIZING GAME STATE" ) scene.incomingStateString = scene:summarizeStateAsString() -- log(1, "Mod sending state: " .. scene.incomingStateString ) scene:sendMapEvent( 'state', "state=" .. scene.incomingStateString ) scene.incomingStateString = "" -- probably best to clean up scene.beatsUntilSendState = 10 -- do it this often, while in the playDOTA coroutine end else -- I am not mod, I just consume incoming state from the moderator --log(1, "Non-Mod pending state: " .. scene.incomingStateString ) if( scene.incomingStateString and scene.incomingStateString ~= "" ) then --log(1, "Non-Mod receiving state: " .. scene.incomingStateString ) scene:applyIncomingGameState( scene.incomingStateString ) scene.incomingStateString = "" -- nothing to do until we see another one end end end, ----------------------- -- SGTTTTTTTTTTTTTTTTNNNNNNNNNNNNNNNNNNNNNNNNN -- S is game state (0,1,2,3) -- G is game over flag (winner) (0,9-West,10-East) -- Ts are the towers, in order, capital letter for alive -- Ns are the NPCs, which again maybe just capital letter for alive summarizeStateAsString = function( scene ) local numTowers = 20 local numMinions = glMaxMinions local towerIx local ix local towerStateString = "" local minionStateString = "" local c -- log( 1, "SUMMARIZE STATE" ) for towerIx=1, numTowers do c = 't' if scene:towerIsAlive( towerIx - 1 ) then c = 'T' end towerStateString = towerStateString .. c end -- log( 1, "SUMMARIZE STATE towers: " .. towerStateString ) for ix=1, numMinions do c = 'n' if scene:minionIsAlive( ix - 1 ) then c = 'N' end minionStateString = minionStateString .. c end -- log( 1, "SUMMARIZE STATE minions: " .. minionStateString ) local stateString = "" .. scene.gameState .. scene.gameOver .. towerStateString .. minionStateString log( 1, "SUMMARIZE STATE state string: " .. stateString ) return stateString end, applyIncomingGameState = function( scene, stateString ) local numTowers = 20 local numMinions = glMaxMinions -- log( 1, "applyIncomingGameState invoked with " .. stateString ) if( stateString ) then local len = string.len( stateString ) --log( 1, "length of state string: " .. len ) local i for i=1, len do local towerIx = (i - 3) -- is 0 for first tower local ix = (i - (3 + numTowers)) -- is 0 for first minion local c = string.sub( stateString, i, i ) if (i == 1) then if scene.gameState ~= (0 + c) then log( 1, "setting gameState to " .. scene.gameState ) end scene.gameState = 0 + c elseif (i == 2) then if scene.gameOver ~= (0 + c) then log( 1, "setting gameOver to " .. scene.gameOver ) end scene.gameOver = 0 + c elseif (towerIx < numTowers) then if ( c == 't' ) then -- lowercase means dead if ( scene:towerIsAlive( towerIx ) ) then log(1, "killing towerIx " .. towerIx .. " because incoming state stream said " .. c ) scene:killTower( towerIx) end end elseif (ix < numMinions) then if ( c == 'n' ) then if ( scene:minionIsAlive( ix ) ) then log(1, "killing minion ix " .. ix .. " because incoming state stream said " .. c ) scene:killMinion( ix ) end end end end end end, -- assumes ix starts at 0 killTower = function(scene, towerIx ) if towerIx then scene.towerState[ towerIx + 1 ] = 0 botTower.id = "tower" .. (glTowerNpcIndexBase + towerIx) log( 1, "killTower/scene " .. scene.id .. ", tower " .. towerIx .. ": " .. botTower.id ) destroyNpc( botTower ) end end, -- assumes ix starts at 0 killMinion = function( scene, ix ) if ix then local minion = glMinionArray[ ix + 1 ] if ( minion ) then log( 1, "killMinion/scene " .. scene.id .. ", minion " .. ix .. ": " .. minion.id ) minion.behaveMask = 0 -- realll, we should clear this when re-using it, but I like it clean now destroyNpc( minion ) -- i suspect I have forgotten some plan minion.isDead = 1 -- so minionIsAlive works end end end, }) -- end of sceneDOTA ------------------------------------------------------------------ -- paranoiacally log some of these to make sure inheritance works log( 1, "------SCENE ROOT --------" ) dumpTable( sceneRoot, "SceneRoot ") log( 1, "------SCENE DOTA --------" ) dumpTable( sceneDOTA, "SceneDota ") --============================================================================= -- 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 --------" ) -- let the log know we're done loading the starmap log(1, '------ DOTC Starmap Script Loaded ------' ) -- end of script