code:
;--[[ ; 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$2100223
355555552310_00000000000000__05525522255520__00535333222200_02302222222101200_220000000022_00__21211
11112__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
-- | | [C} | [A} - gate friendly to EAST
-- | | | |
-- 6---{D]---7---------8 9--------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,
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 " )
scene.beatsUntilSendState = 10 -- postpone/schedule state sending
-- 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( )
wait( 5 )
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( 5 ) -- start slow
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( 5 )
end
wait(4) -- superstition?
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,
-- 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
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( "playDOTA" ) -- 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
['playDOTA'] = function( scene, args )
log( 1, "playDOTA 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
-- playCutscene( scene, scene.playDOTA ) -- starts the coroutine lasts the entire game
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
-- now, if I just got here, I am probably not even running playDOTA coroutine, and
-- I need that to happen
if scene.gameState == 0 then
-- I don't really expect state while I am in state 0
-- so this will override any running cutscene (bots are still independent)
if( args.fromMod == 1 ) then
-- we should already be in the coroutine, though it wouldn't hurt to check and THEN restart
--glWasPlayingDOTA = 0 -- this means a fresh launch of a new session
--scene.resurrect = 0 -- and it's our start
--playCutscene( scene, scene.playDOTA ) -- starts the coroutine lasts the entire game
end
end
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 it's victory!" );
else
announce( "Factory EAST exults in it's 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