Version 16
[Hedgewars_Game_Hacks.git] / Game_Hacks_v16.lua
blob751fa30bdec77775990af50f53ff813a7ca6a588
1 --[[
2 Game Hacks
4 INTRODUCTION
5 This is a style which allows you to manipulate over 100 different aspects of Hedgewars
6 using the script parameter in the game scheme.
7 Game Hacks is supported for Hedgewars version 0.9.22 and later, some additional parameters
8 are available from 0.9.23.
10 DETAILED DESCRIPTION
11 Game Hacks adds additional game modifiers and settings in the following categories:
12 - Environment
13 - Hedgehogs
14 - Turn-based play
15 - Sudden Death events
16 - Weapon parameters, including per-weapon damage (for 0.9.23 or later)
17 - Utility parameters
18 - Eye-candy/gimmicks
20 The most important enabled modifiers will be displayed and explained briefly
21 in the mission panel shown at the start of the game or when you hit the exit
22 key (default: [Esc]). As a rule of thumb, modifiers which are not obvious and
23 have a big impact on gameplay are mentioned. Modifiers which are either obvious
24 or only have little or no impact are usually nor mentioned. Also, most modified
25 weapons and utility effects are usually not mentioned.
27 One important rule: If there is a conflict between one of Hedgewars'
28 built-in settings and a script setting, the script setting takes precedence.
30 With an empty script parameter, the style only does cosmetic changes:
31 - Messages show number of remaining Birdy eggs and RC plane bombs
32 - Game modifier “Wind influences almost everything” is shown in mission panel
33 - Generic ammo symbols for Extra Time and Extra Damage
35 BASIC SYNTAX
36 This script can be configured mostly with the script parameters.
37 The script parameters are specified in a key=value format each,
38 and each pair is delimeted by a comma.
39 Each value has one of multiple parameter types,
40 which are described below.
41 Each key can be specified in long form or in short form. The long form is descriptive.
42 The short form is only 1 or 2 characters long, but harder to remember.
44 Note that the entire script parameter field should not become longer than 245 characters, longer input can lead
45 to problems in network games. This is a problem of Hedgewars. If you are using a lot of keys, you can use the
46 short form. However, even with the short form it is possible to exhaust the character limit if you use a lot of
47 parameters, so keep this in mind.
49 It is recommended to use the long form when it is possible, because the short form is hard to remember and read.
51 All values are optional.
52 See also the “EXAMPLES” section below.
54 Parameter types (see list of keys):
55 num:
56 A whole number.
57 Examples: 534, 54, -13
58 hex:
59 A whole non-negative number in hexadecimal notation. Valid digits are 0-9 and A-F or a-f.
60 Examples: E, FF, 84AE, a0a0a0
61 time:
62 Time. Times are interpreted as milliseconds by default, but you can append “ms” or “s” for milliseconds
63 or seconds, respectively. Seconds can be specified as decimal numbers with “.” as the decimal seperator
64 and with up to 3 decimal places, but milliseconds must always be whole numbers.
65 Examples (all mean 2.5 seconds): 2500, 2500ms, 2.5s
67 A percentage or raw number. If you append a percent sign to the number, it is interpreted as percentage
68 from the default raw number. Only use whole numbers for percentages. Note that specifying the raw number
69 may be much more precise, but for most of the time, specifying the percentage might be precise enough for you.
70 Examples: 2000, 100%, 125%
71 truth:
72 Used for on/off states. Can be either “true” (feature is enabled) or “false” (feature is disabled).
73 You could also just use the first letter of the words to save space.
74 choice:
75 You can choose one of multiple textual values which is written in the description.
78 In the table below, some types will have an asterisk (“*”). This means that the special value “inf” (or short “i”)
79 is allowed here, meaning a infinite amout or time.
80 Some types will have a dash (“-”). This means that you can also specify a range of values by writing
81 two values of this type seperated by a dash (e.g. “4-52”). The first value must be smaller or equal than the second
82 value, otherwise they will be ignored.
84 Some parameters permit special values, see the description.
85 Most parameters out of reasonable bounds (i.e. a barrel health of 0 or negative gravity) will be capped for technical
86 reasons to avoid a buggy or unexpected behaviour. This is a security feature but can be disabled if you set forcelimits
87 to false. WARNING: A correct operation of Game Hacks is NOT guaranteed with forcelimits=false since you would be
88 allowed to enter nonsensical values.
90 LIST OF KEYS
92 Key Short Type Default Description
93 -----------------------+-------+-------+-------+-------------------------------------------------------------------------
94 <<< ENVIRONMENT >>>
95 barrelhealth b num 60 Initial health of barrels.
96 healthcratehealth eh num- N/A Health in health crates will be randomly set within this range
97 sturdycrates ec truth false Crates can't be destroyed.
98 maxwind w % 100 Limit the maximum wind strength (0-100).
99 fairwind W truth false Wind stays the same within the same round.
100 gravity g % 100 Base gravity (without utilities) in percent.
101 girders G num 0 Number of randomly placed girders, not all might be placed if too many. EXPERIMENTAL!
102 airflameshurt a truth false Flames in mid-air can hurt hedgehogs.
103 stickyflames ef choice default “always”: All flames keep burning through turns. “never”: No flames do that. “default”: Flames are unchanged.
104 nosnow Q truth false Snow does not pile up in themes where it normally snows (i.e. Snow, Christmas)
106 <<< HEDGEHOGS >>>
107 nojump j truth false Hogs can't jump.
108 noknock k truth false “Hogs of Steel”: Hogs can't be knocked away by sliding hogs.
109 hogfriction 0 num 0 Difference from default hog friction. Sane values are from -10 to 10.
110 poisondamage P num 5 Damage for poisoned hogs per round.
111 poisonkills n truth false Poison reduces health down to 0 instead of 1.
112 poison p truth false All hedgehogs start poisoned.
113 epidemic he truth false Poisoned hedgehogs infect other hedgehogs they touch.
114 teamcure t truth false Collecting a health crate will remove poison from all hogs in team.
115 sharehealth h truth false Health of crates is shared among team mates.
116 davidandgoliath d truth false The 1st hog in team removes half the health of its team mates and adds it to its own.
117 donorbox q truth false The last dying hog (or the king in King Mode) of a team leaves a box with all the hog weapons inside.
118 maxpower hm truth false Thrown/launched weapons will be thrown/launched at full power (like mortar).
120 <<< TURNS >>>
121 ready r time 5s Ready time before the turn starts.
122 initialswitch i truth false Free hog selection at start of turn. EXPERIMENTAL!
123 strategictools z truth false Girders, rubbers, landsprays, dirt balls and resurrections end the turn.
125 <<< SUDDEN DEATH >>>
126 sdpoison S truth false Poison all hogs in Sudden Death (once).
127 sd1hp 1 truth false Set the health of all hogs to 1 HP in Sudden Death (once).
129 <<< FUEL / DURATION >>>
130 saucerfuel f %* 2000 Fuel of a flying saucer.
131 birdyenergy B %* 2000 Flapping energy of Birdy.
132 landsprayfuel L % 1000 Fuel of landspray.
133 freezerfuel u % 1000 Fuel of freezer.
134 flamethrowerfuel F % 500 Fuel of flamethrower.
135 rcplanetimer C time* 15s Time of RC plane before fuel is gone.
136 minetimer 8 time- 3s Timer of ALL ordinary mines, with milliseconds precision. Use range for a random timer within the range.
137 stickyminetimer s time- 500ms Timer of sticky mines. Use range for a random timer within the range.
138 airminetimer Y time- 750ms Custom timer of air mines. Use range for a random timer within the range.
139 hellishtimer 6 time 5s Timer of hellish hand-grenade. Use “manual” or “m” for allowing to set timer by user.
140 dynamitetimer 5 time 5s Timer of dynamite. Use “manual” or “m” for allowing to set timer by user.
141 caketimer c % 2048 Walking time of cake (percentage).
142 beedelay x time 500ms Delay of homing bee before it starts homing.
143 beetimer y time 5s Max. flight time of homing bee after it started homing
144 drillrockettimer 3 time 5s Detonation timer of drill rocket (not from plane). Use “manual” or “m” for allowing to set timer by user.
145 napalmbombtimer tN time 1s Detonation timer of a napalm bomb.
146 kamikazerange K % 2048 Range of kamikaze.
147 balltimer m time 5s Explosion timer of ball from ball gun. Use “manual” or “m” for allowing to manually set timer with the ball gun.
148 pickhammertimer I time* 4s Active time of pickhammer.
149 blowtorchtimer T time* 7.5s Active time of blowtorch.
150 poisoncloudtimer tp time 5s Life timer of a poison cloud from old limburger. 5 seconds max.
152 <<< WEAPON EFFECTS >>>
153 birdyeggs E num* 2 Number of eggs for Birdy
154 rcplanebombs R num* 3 Number of bombs of RC plane.
155 ballgunballs U num 51 Number of balls from ballgun.
156 pianobounces O num 5 Number of terrain bounces of piano before it falls into water.
157 airattackbombs A num 6 Bombs in an air attack.
158 minestrikemines M num 6 Mines in a mine strike.
159 drillstrikedrills D num 6 Drills in a drill strike.
160 napalmbombs N num 6 Bombs in a napalm attack.
161 planedrops 4 num 6 Shorthand: Set all above 4 parameters at once.
162 airattackgap wA % 30 Distance between 2 missile drops in air attack.
163 minestrikegap wM % 30 Distance between 2 mine drops in mine strike.
164 drillstrikegap wD % 30 Distance between 2 drill drops in drill strike.
165 napalmgap wN % 30 Distance between 2 napalm bomb drops in napalm attack.
166 planedropgap 2 % 30 Shorthand: Set all above 4 parameters at once.
167 dudminehealth wu % 35 How much “health” dud mines have before they explode.
168 seductionrange 7 % 250 Range of seduction in pixels. EXPERIMENTAL!
169 airmineseekrange wr %* 175 Distance (in pixels) at which air mines start seeking.
170 airmineseekspeed we % 536871 Speed at which air mines seek hogs.
171 airminefriction wf % 1610613 Air friction of air mine.
172 hammerstrength wh %* 125 How deep a (successful) hammer hit digs into land.
173 kamikazetrigger wk truth false If true, kamikaze can be detonated early with the attack key.
174 deaglestrength wd % 50 How deep a desert eagle shot digs into land.
175 sniperstrength ws % 50 How deep a sniper rifle shot digs into land.
176 sinestrength wS num 1 Thickness of sine gun shot.
177 mudballstrength wm % 200000 Pushing strength of a mud ball. 0.9.23 or later!
179 <<< DAMAGE AND EXPLOSION SIZE >>>
180 --- NOTE: Except for airminedamage, all of these are for 0.9.23 or later!
182 airminedamage da % 30 Explosion damage and range of air mine.
183 minedamage d8 % 50 Mine damage.
184 stickyminedamage dt % 30 Sticky mine damage.
185 grenadedamage dg % 50 Grenade damage.
186 hellishdamage dH % 90 Hellish hand-grenade damage.
187 clusterbombdamage dl % 20 Cluster bomb damage (main bomb).
188 clusterdamage dL % 25 Damage of a cluster (from cluster bomb or mortar).
189 melondamage dm % 75 Melon bomb damage (main piece).
190 melonpiecedamage dM % 75 Melon bomb piece damage.
191 balldamage dU % 40 Damage of a ball from the ball gun.
192 bazookadamage db % 50 Bazooka shell damage.
193 mortardamage do % 20 Mortar damage (main projectile).
194 beedamage dx % 50 Bee damage.
195 dynamitedamage d5 % 75 Dynamite damage.
196 cakedamage dc % 75 Cake damage.
197 batdamage dB % 30 Baseball bat damage.
198 shoryukendamage dF % 30 Shoryuken damage.
199 whipdamage dw % 30 Whip damage.
200 rcplanedamage dr % 25 RC plane damage.
201 cleaverdamage dv % 40000 Scaling value of the impact damage of a cleaver. The damage does not linearly increase.
202 eggdamage dE % 10 Damage of an egg (from Birdy).
203 pianodamage dp % 80 Damage of a single Piano explosion (note: each impact creates 3 explosions).
204 limburgerdamage di % 20 Old limburger damage.
205 deagledamage dd % 7 Damage of a single Desert Eagle shot.
206 shotgundamage dO % 25 Damage of a single shotgun shot.
207 sniperdamage ds % 100000 Scaling value of the sniper rifle's damage.
208 sinedamage dS % 35 Damage of a sine wave from the sine gun.
209 kamikazedamage dK % 30 Damage of a kamikaze impact and its explosion.
210 airbombdamage dA % 30 Damage of a bomb from an air strike or en RC plane.
211 drillrocketdamage d3 % 50 Damage of a launched drill rocket.
212 drillstrikedrilldamage dD % 30 Damage of a drill rocket from the drill strike.
213 blowtorchdamage dT % 2 Damage of a single hit with a blowtorch.
214 pickhammerdamage dI % 6 Damage of a single hit with a pickhammer.
215 flamedamage df % 2 Damage of a small flame (larger flames deal 3 or 4 times this damage)
216 hogdamage dh % 30 Damage of the explosion of a dying hedgehog.
217 cratedamage dC % 25 Damage of an exploding crate.
218 barreldamage de % 75 Damage of an exploding barrel.
219 hammerdamage dj num 3 Denominator of the hammer damage. Actual damage will be 1/<number>*<health>.
220 hammerextradamage dJ num 2 Denominator of the hammer damage while extra damage is on. Actual damage will be 1/<number>*<health>.
222 <<< UTILITY EFFECTS >>>
223 buildrange J %* 256 Max. building range of girder.
224 extratime e time 30s Time you get with Extra Time utility.
225 lowgravity l % 50 Percentage of base gravity used when low gravity utility is used.
226 resurrectorrange 9 % 100 Range of resurrector in pixels. EXPERIMENTAL!
227 timeboxturns ut num* N/A TimeBox will return after this many additional turns. See parameter notes for details.
228 pickhammerstraightdown up truth false If true, the pick hammer will be able to dig straight down only and it direction can not be changed.
229 buildlimit ub num* inf How many constructions, rubbers and landsprays can be used per turn (inf = no limit).
231 <<< EYE CANDY / GIMMICKS >>>
232 shoppaborder o truth false Landscape has a decorative “danger” stripe on its edges.
233 ropecolor xr hex D8D8D8 Rope color (will slightly change rope style). Use value “clan” to make rope appear in clan color.
234 ropeopacity xR % 255 Opacity of ropes (will slighty change rope style). 255=full opacity.
236 <<< SCRIPT CONFIGURATION >>>
237 forcelimits Z truth true Cap most number-based values at reasonable bounds. WARNING: If false, correct operation is NOT guaranteed
238 hedgepot H truth false Change 1-5 random game settings and 0-1 weapons/utilities.
242 PARAMETER NOTES
244 strategictools:
245 - Minor annoyance: The hedgehog will have skip turn selected in the next turn after a girder was placed
246 - Disables infinite attack mode.
248 initialswitch:
249 - It is still a bit awkward to use, i.e. it is not active in the “ready” phase.
251 seductionrange, resurrectorrange:
252 - Two circles will be drawn.
253 - The brighter shows the actual range, the other circle is the original range
255 girders:
256 - Girders will be placed with a small buffer zone around them to avoid girders going to close to things and
257 each other
258 - Hedgehogs and objects will NOT spawn on the girders; girders are inserted after everything else has been
259 generated and placed
260 - If the girders would become too crammed in the landscapes, Game Hacks decides to stop adding more girders
261 - With forcelimits=false, the above limit does not apply, but the loading time might increase considerably
262 for a huge number of girders (>100000)
263 - Known quirks:
264 - Possibly poor girder placement for custom and “unorthodox” girder shapes from a theme
265 - Considered experimental, the behaviour of this parameter may change in future
267 planedrops, planedropgap:
268 - If you additionally explicitly specify parameters for the explicit plane drops,
269 (airattackbombs, airattackgap, etc.) those explicit values take precedence
271 gravity, lowgravity:
272 - Low gravity utility will modify gravity based on the base gravity
273 - Disables built-in low gravity game modifier
275 hogfriction:
276 - Frictions determines how “well” the hogs slide, low friction means more sliding power
277 - Note that this scale is not linear
278 - Use these values as guide:
279 * 9989=max. friction (hedgehogs don't slide at all)
280 * 20=very high friction
281 * 10=high friction
282 * 0=default friction
283 * -5=low friction
284 * -8=very low friction
285 * -10=extremely low friction
286 - Use values in-between for fine-tuning
288 gravity, lowgravity, maxwind:
289 - It does not matter if you append a percent sign or not
291 fairwind, maxwind:
292 - Overwrites the built-in “disable wind” game modifier
294 minetimer:
295 - Will overwrite the default mine timer in the game scheme editor
296 - Air mine timers will default to 750ms if not specified with airminetimer
297 - The built-in mine timer setting will also affect the air mine timer
298 - Use this setting when you need precision of 1 millisecond
299 - Use the built-in setting when you need precision of 1 second only
300 - Only use this key when you really need it
302 dynamitetimer:
303 - Animation is supported for up to 10s
304 - Longer times are perfectly okay, but the dynamite is not animated until timer is at 10s or lower.
306 poisoncloudtimer:
307 - Set forcelimits to false if you need to use timers longer than 5s.
308 - However: Times above 5s are glitchy, the color and transparency of the poison cloud become awkward.
309 - Use a time of 0 for a poison-free explosion of the old limburger.
311 barrelhealth:
312 - Barrels will start frozen if health is above 60 (this is only a graphical effect).
314 sd1hp, sdpoison:
315 - Be aware that the normal Sudden Death health decrease can still apply on top of these
316 effects. It is recommended (but not mandatory) to set it to 0
317 - If both Sudden Death health decrease and water rise are set to 0, the Sudden Death effect will
318 be faked by the script but the background won't change
320 sharehealth:
321 - Health is shared as equally as possible. If the division leaves a remainder, the remainder will
322 be split among some of the hedgehogs, beginning with the current hog and continuing with the next ones
324 healthcratehealth:
325 - If this is set, the health crate amount set in the game scheme will be ignored
327 davidandgoliath
328 - If the division leaves a remainder, the result is rounded down
330 donorbox:
331 - The donor box will replace the grave
333 timeboxturns:
334 - With value 0, the TimeBox returns as fast as possible which will be in the next turn, turning the TimeBox basically into a random teleportation
335 - Value 1 refers to the turn after the next turn, and so on.
336 - With the value “inf”, the TimeBox will return only (!) in Sudden Death or when this is the final hog in the team, not earlier
337 - Regardless of settings, the TimeBox follows the standard TimeBox behaviour of always returning in Sudden Death or when it is the final hog in the team
339 EXAMPLES
341 saucerfuel=4000
342 --> The flying saucer has 200% fuel.
344 saucerfuel=200%
345 --> The flying saucer has 200% fuel.
347 saucerfuel=inf
348 --> The flying saucer has infinite fuel.
350 stickyminetimer=0, nojump=true
351 --> Sticky mines explode instantly and jumping is disabled.
353 minetimer=1s-2s
354 --> Mines have a random time which may be between 1 and 2 seconds long.
356 ready=10s
357 --> The ready timer is set to 10 seconds.
359 poison=true, poisondamage=10
360 --> All hedgehogs are poisoned from the beginning and take 10 poison damage per turn.
362 gravity=200
363 --> The base gravity is doubled. It will go down to 100% when low gravity utility is used in-game (50% of base gravity).
365 gravity=200%, lowgravity=25%
366 --> The base gravity is doubled. It will go down to 25% of that base gravity (!) when the low gravity utility is used, which is 50% of the default gravity.
368 g=200%,l=25%
369 --> Same as the previous example, but it uses the short form.
371 ropecolor=FF0000
372 --> All ropes are red.
377 HedgewarsScriptLoad("/Scripts/Locale.lua")
378 HedgewarsScriptLoad("/Scripts/Params.lua")
379 HedgewarsScriptLoad("/Scripts/Tracker.lua")
381 -- Save several gears
382 local birdyGear = nil
383 local rcPlaneGear = nil
384 local saucerGear = nil
385 local kamikazeGear = nil
386 local planeGears = {}
387 local beeGears = {}
388 local mineGears = {}
389 local ropeGear = nil
390 local dynaGears = {}
391 local healthCaseGearsTodo = {} -- List of health crates for which the health still needs to be set
392 local hogGears = {}
393 local poisonCloudGears = {}
394 local tardisGears = {}
395 local specialJumpingGear = nil -- Save gear for “no jumping” mode
397 -- Serveral status variables
398 local gameStarted = false
399 local setAirPlaneHealth = false
400 local switchStage = 0
401 local hogCounter = 0
402 local roundWind -- Used for fair wind
403 local lastRound -- TotalRounds value for last turn
404 local sdState = 0 -- Save Sudden Death state (0=not active, 1=starts in next turn, 2=active)
405 local girderPlaced -- Used for strategic tools
406 local teams = {} -- Table indexed by team names, values are number of hogs (at start of game)
407 local poisonKillDone = false -- Has the “poison kills” modifier been applied in this turn yet?
408 local donors = {} -- Table of hogs which are about to “donate”
409 local donorBoxes = {} -- Table of donor boxes, values are tables which contain ammo counts
410 local flaggedGrave -- Remember a grave for 1 tick, used by donor boxes
411 local hogNumbers = {}
412 local lastConstruction -- Remember last placed girder or rubber. Used for girder/rubber range
413 local rangeCircle -- Used for seduction and resurrector
414 local manualTimer -- Used to save the last manual timer used by the user
415 local setWeaponMessage
416 local releaseShotInNextTurn -- Temporary variable for max. launching power
417 local stuffBuilt = 0 -- How many constructions, rubbers and landsprays have been used this turn (used for constructionlimit)
418 local extraDamageUsed = false -- Whether extra damage has been used in this turn
420 local tmpSwitchCount
421 local tmpSelectedAmmoType
423 -- List of weapons that have a power meter; used by maxPower
424 -- Longterm TODO: Keep this up-to-date for new Hedgewars releases
425 local powerWeapons = { amGrenade, amClusterBomb, amBazooka, amBee, amWatermelon,
426 amHellishBomb, amDrill, amMolotov, amGasBomb, amSMine, amSnowball, amKnife,
427 amAirMine }
429 -- More variables grabbed from the engine
430 local cMaxWindSpeed_QWORD = 1073742
431 local cMaxPower = 1500
434 --[[ Parser helpers ]]
436 function minmax(n, min, max)
437 if forceLimits then
438 if max and min then
439 return math.min(max, math.max(min, n))
440 elseif max then
441 return math.min(max, n)
442 elseif min then
443 return math.max(min, n)
444 else
445 return n
447 else
448 -- This happens if the “unreasonable values” protection is off
449 return n
453 function parseRange(key, str, unitParser, ...)
454 if str == nil then return nil end
455 local minval, maxval
456 subMin, subMax = string.match(str, "([^-]+)-([^-]+)")
457 if subMin ~= nil and subMax ~= nil then
458 minval = unitParser(key, subMin, ...)
459 maxval = unitParser(key, subMax, ...)
460 else
461 minval = unitParser(key, str, ...)
462 maxval = minval
464 if minval ~= nil and maxval ~= nil then
465 if minval > maxval then
466 minval = nil
467 maxval = nil
470 return minval, maxval
474 function parsePercentOrNumber(key, str, reference, min, max)
475 if str == nil then return nil end
477 local n
478 if str == "inf" or str == "i" then
479 return "inf"
480 elseif string.sub(str, string.len(str), string.len(str)) == "%" then
481 local substr = string.sub(str, 1, string.len(str)-1)
482 n = truncStrNum(substr)
483 if type(n) == "number" then
484 local ret = div(n * reference, 100)
485 return minmax(ret, min, max)
486 else
487 return nil
489 else
490 n = truncStrNum(str)
491 if type(n) == "number" then
492 return minmax(n, min, max)
493 else
494 return nil
499 --[[
500 - Leave original value if last characters are “ms”
501 - Multiply by 1000 if last character is “s”
502 - Leave original value if raw number was supplied
503 - min and max apply to raw number ]]
504 function parseTime(key, str, min, max)
505 if str == nil then return nil end
507 local n
508 local len = string.len(str)
510 -- infinite time
511 if str == "inf" or str == "i" then
512 return "inf"
513 -- milliseconds
514 elseif string.sub(str, len-1, len) == "ms" then
515 local substr = string.sub(str, 1, len-2)
516 n = truncStrNum(substr)
517 -- seconds
518 elseif string.sub(str, len, len) == "s" then
519 local substr = string.sub(str, 1, len-1)
520 local prePoint, postPoint = string.match(substr, "(%d*)%.(%d*)")
521 if postPoint == nil then
522 prePoint = string.match(substr, "(%d*)")
523 postPoint = "000"
525 prePoint = tonumber(prePoint)
527 if prePoint == nil then
528 return nil
531 local postPointN = {}
533 for i=1,3 do
534 local subN = string.sub(postPoint, i, i)
535 if tonumber(subN) ~= nil then
536 postPointN[i] = tonumber(subN)
537 else
538 postPointN[i] = 0
542 n = prePoint * 1000
544 for i=1,3 do
545 n = n + postPointN[i] * math.pow(10, 3-i)
550 if type(n) ~= "number" then return nil end
551 -- raw number (interpreted as milliseconds)
552 else
553 n = truncStrNum(str)
555 if type(n) ~= "number" then
556 return nil
558 return minmax(n, min, max)
561 function parseTimeRange(key, str, min, max)
562 return parseRange(key, str, parseTime, min, max)
565 function parseInt(key, str, min, max)
566 if str == "inf" or str == "i" then
567 return "inf"
568 else
569 n = truncStrNum(str)
570 if n ~= nil then
571 return minmax(n, min, max)
572 else
573 return nil
578 function parseIntRange(key, str, min, max)
579 return parseRange(key, str, parseInt, min, max)
582 function parseHex(key, str, min, max)
583 if str == nil then return nil end
584 local s = string.match(str, "(%x*)")
585 if s == nil then return nil end
586 local n = tonumber("0x"..str)
587 if str == "inf" or str == "i" then
588 return "inf"
589 elseif type(n) == "number" then
590 return minmax(n, min, max)
591 else
592 return nil
596 function parseBool(key, str, default)
597 if str == "true" or str == "t" then
598 return true
599 elseif str == "false" or str == "f" then
600 return false
601 else
602 return nil
606 function multiParse(keys, parseFunction, ...)
607 for i=1,#keys do
608 local result = parseFunction(keys[i], params[keys[i]], ...)
609 if result ~= nil then
610 return parseFunction(keys[i], params[keys[i]], ...)
613 return nil
616 function onParameters()
617 parseParams()
619 --[[ DEVELOPER NOTE:
620 Remove a free letter (for the short form) from this list when you introduce a new parameter with a 1-letter short form.
621 Free letters:
622 v, V
625 -- Script config parameters first
626 forceLimits = multiParse({"forcelimits", "Z"}, parseBool)
627 if forceLimits == nil then forceLimits = true end
628 hedgePot = multiParse({"hedgepot", "H"}, parseBool)
630 -- Now for all the other params
632 if params["minetimer"] == "random" or params["8"] == "random" or params["minetimer"] == "r" or params["8"] == "r" then
633 mineTimerMin = 0
634 mineTimerMax = 5000
635 else
636 mineTimerMin, mineTimerMax = multiParse({"minetimer", "8"}, parseTimeRange, 0)
638 if params["stickyminetimer"] == "random" or params["s"] == "random" or params["stickyminetimer"] == "r" or params["s"] == "r" then
639 stickyMineTimerMin = 0
640 stickyMineTimerMax = 3000
641 else
642 stickyMineTimerMin, stickyMineTimerMax = multiParse({"stickyminetimer", "s"}, parseTimeRange, 0)
644 if params["airminetimer"] == "random" or params["Y"] == "random" or params["airminetimer"] == "r" or params["Y"] == "r" then
645 airMineTimerMin = 0
646 airMineTimerMax = 1300
647 else
648 airMineTimerMin, airMineTimerMax = multiParse({"airminetimer", "Y"}, parseTimeRange, 0)
650 if params["stickyflames"] == "always" or params["stickyflames"] == "never" then
651 stickyFlames = params["stickyflames"]
652 elseif params["ef"] == "always" or params["ef"] == "never" then
653 stickyFlames = params["ef"]
656 healthCaseAmountMin, healthCaseAmountMax = multiParse({"healthcratehealth", "eh"}, parseIntRange, 0)
657 sturdyCrates = multiParse({"sturdycrates", "ec"}, parseBool)
659 gravity = multiParse({"gravity", "g"}, parsePercentOrNumber, 100, 1)
660 dudMineHealth = multiParse({"dudminehealth", "wu"}, parsePercentOrNumber, 35, 1)
661 barrelHealth = multiParse({"barrelhealth", "b"}, parseInt, 1)
662 extraTime = multiParse({"extratime", "e"}, parseTime, 1000)
663 saucerFuel = multiParse({"saucerfuel", "f"}, parsePercentOrNumber, 2000, 1)
664 birdyEnergy = multiParse({"birdyenergy", "B"}, parsePercentOrNumber, 2000, 1)
665 landsprayFuel = multiParse({"landsprayfuel", "L"}, parsePercentOrNumber, 1000, 1)
666 freezerFuel = multiParse({"freezerfuel", "u"}, parsePercentOrNumber, 1000, 1)
667 flamethrowerFuel = multiParse({"flamethrowerfuel", "F"}, parsePercentOrNumber, 500, 1)
668 rcPlaneTimer = multiParse({"rcplanetimer", "C"}, parseTime, 1)
669 rcPlaneBombs = multiParse({"rcplanebombs", "R"}, parseInt, 0)
670 birdyEggs = multiParse({"birdyeggs", "E"}, parseInt, 0)
671 pianoBounces = multiParse({"pianobounces", "O"}, parseInt, 1)
672 ballGunBalls = multiParse({"ballgunballs", "U"}, parseInt, 1)
673 cakeTimer = multiParse({"caketimer", "c"}, parsePercentOrNumber, 2048, 1)
674 kamikazeRange = multiParse({"kamikazerange", "K"}, parsePercentOrNumber, 2048, 0)
675 kamikazeTrigger = multiParse({"kamikazetrigger", "wk"}, parseBool)
676 blowTorchTimer = multiParse({"blowtorchtimer", "T"}, parseTime, 1)
677 pickHammerTimer = multiParse({"pickhammertimer", "I"}, parseTime, 1)
678 planeDrops = multiParse({"planedrops", "4"}, parseInt, 1)
679 airAttackBombs = multiParse({"airattackbombs", "A"}, parseInt, 1) or planeDrops
680 napalmBombs = multiParse({"napalmbombs", "N"}, parseInt, 1) or planeDrops
681 mineStrikeMines = multiParse({"minestrikemines", "M"}, parseInt, 1) or planeDrops
682 drillStrikeDrills = multiParse({"drillstrikedrills", "D"}, parseInt, 1) or planeDrops
683 planeDropGap = multiParse({"planedropgap", "2"}, parsePercentOrNumber, 30, 1)
684 airAttackGap = multiParse({"airattackgap", "wA"}, parsePercentOrNumber, 30, 1) or planeDropGap
685 napalmGap = multiParse({"napalmgap", "wN"}, parsePercentOrNumber, 30, 1) or planeDropGap
686 mineStrikeGap = multiParse({"minestrikegap", "wM"}, parsePercentOrNumber, 30, 1) or planeDropGap
687 drillStrikeGap = multiParse({"drillstrikegap", "wD"}, parsePercentOrNumber, 30, 1) or planeDropGap
688 napalmBombTimer = multiParse({"napalmbombtimer", "tN"}, parseTime, 0)
689 girders = multiParse({"girders", "G"}, parseInt, 0)
690 ready = multiParse({"ready", "r"}, parseTime, 0)
691 airFlamesHurt = multiParse({"airflameshurt", "a"}, parseBool)
692 maxWind = multiParse({"maxwind", "w"}, parsePercentOrNumber, 100, 0, 100)
693 lowGravity = multiParse({"lowgravity", "l"}, parsePercentOrNumber, 100, 1)
694 noJumping = multiParse({"nojump", "j"}, parseBool)
695 noSnow = multiParse({"nosnow", "Q"}, parseBool)
696 initialSwitch = multiParse({"initialswitch", "i"}, parseBool)
697 strategicTools = multiParse({"strategictools", "z"}, parseBool)
698 maxPower = multiParse({"maxpower", "hm"}, parseBool)
699 shoppaBorder = multiParse({"shoppaborder", "o"}, parseBool)
700 poison = multiParse({"poison", "p"}, parseBool)
701 epidemic = multiParse({"epidemic", "he"}, parseBool)
702 poisonDamage = multiParse({"poisondamage", "P"}, parseInt, 1)
703 poisonKills = multiParse({"poisonkills", "n"}, parseBool)
704 teamCure = multiParse({"teamcure", "t"}, parseBool)
705 shareHealth = multiParse({"sharehealth", "h"}, parseBool)
706 davidAndGoliath = multiParse({"davidandgoliath", "d"}, parseBool)
707 donorBox = multiParse({"donorbox", "q"}, parseBool)
708 fairWind = multiParse({"fairwind", "W"}, parseBool)
709 noKnock = multiParse({"noknock", "k"}, parseBool)
710 sdOneHP = multiParse({"sd1hp", "1"}, parseBool)
711 sdPoison = multiParse({"sdpoison", "S"}, parseBool)
712 poisonCloudTimer = multiParse({"poisoncloudtimer", "tp"}, parseTime, 0, 5000)
713 beeTimer1 = multiParse({"beedelay", "x"}, parseTime, 1)
714 beeTimer2 = multiParse({"beetimer", "y"}, parseTime, 1)
715 -- timeboxnotimer is a legacy parameter which is still unofficially supported;
716 -- users should now use timeboxturns instead
717 tardisReturnEmergencyOnly = multiParse({"timeboxnotimer", "uT"}, parseBool)
718 tardisReturnTurns = multiParse({"timeboxturns", "ut"}, parseInt, 0)
719 -- Fall-back condition for the legacy parameter timeboxnotimer
720 if(tardisReturnTurns == nil and tardisReturnEmergencyOnly ~= nil) then
721 -- legacy timeboxnotimer is the equivalent of timeboxturns=inf
722 tardisReturnTurns = "inf"
724 pickHammerStraightDown = multiParse({"pickhammerstraightdown", "up"}, parseBool)
725 buildLimit = multiParse({"buildlimit", "ub"}, parseInt, 1)
727 if params["balltimer"] == "m" or params["m"] == "m" or params["balltimer"] == "manual" or params["m"] == "manual" then
728 ballTimer = "manual"
729 else
730 ballTimer = multiParse({"balltimer", "m"}, parseTime, 1)
732 deagleStrength = multiParse({"deaglestrength", "wd"}, parsePercentOrNumber, 50)
733 sniperStrength = multiParse({"sniperstrength", "ws"}, parsePercentOrNumber, 50)
734 hammerStrength = multiParse({"hammerstrength", "wh"}, parsePercentOrNumber, 125, 1)
735 if params["hellishtimer"] == "m" or params["6"] == "m" or params["hellishtimer"] == "manual" or params["6"] == "manual" then
736 hhgTimer = "manual"
737 else
738 hhgTimer = multiParse({"hellishtimer", "6"}, parseTime, 1)
740 if params["dynamitetimer"] == "m" or params["5"] == "m" or params["dynamitetimer"] == "manual" or params["5"] == "manual" then
741 dynamiteTimer = "manual"
742 else
743 dynamiteTimer = multiParse({"dynamitetimer", "5"}, parseTime, 0)
745 airMineFriction = multiParse({"airminefriction", "wf"}, parsePercentOrNumber, div(cMaxWindSpeed_QWORD*3, 2), 1)
746 airMineSeekSpeed = multiParse({"airmineseekspeed", "we"}, parsePercentOrNumber, div(cMaxWindSpeed_QWORD, 2), 1)
747 airMineSeekRange = multiParse({"airmineseekrange", "wr"}, parsePercentOrNumber, 175, 0)
748 airMineDamage = multiParse({"airminedamage", "da"}, parsePercentOrNumber, 30, 0)
749 if params["drillrockettimer"] == "m" or params["3"] == "m" or params["drillrockettimer"] == "manual" or params["3"] == "manual" then
750 drillRocketTimer = "manual"
751 else
752 drillRocketTimer = multiParse({"drillrockettimer", "3"}, parseTime, 0)
755 -- Parameters for 0.9.22 or later
756 if SetGearFriction ~= nil then
757 hogFriction = multiParse({"hogfriction", "0"}, parseInt, -10, 9989)
759 -- 0.9.23 or later
760 if SetGearValues ~= nil then
761 -- Damage
762 mineDamage = multiParse({"minedamage", "d8"}, parsePercentOrNumber, 50, 0)
763 stickyMineDamage = multiParse({"stickyminedamage", "dt"}, parsePercentOrNumber, 30, 0)
764 grenadeDamage = multiParse({"grenadedamage", "dg"}, parsePercentOrNumber, 50, 0)
765 hhgDamage = multiParse({"hellishdamage", "dH"}, parsePercentOrNumber, 90, 0)
766 clusterBombDamage = multiParse({"clusterbombdamage", "dl"}, parsePercentOrNumber, 20, 0)
767 clusterDamage = multiParse({"clusterdamage", "dL"}, parsePercentOrNumber, 25, 0)
768 melonDamage = multiParse({"melondamage", "dm"}, parsePercentOrNumber, 75, 0)
769 melonPieceDamage = multiParse({"melonpiecedamage", "dM"}, parsePercentOrNumber, 75, 0)
770 ballDamage = multiParse({"balldamage", "dU"}, parsePercentOrNumber, 40, 0)
771 bazookaDamage = multiParse({"bazookadamage", "db"}, parsePercentOrNumber, 50, 0)
772 mortarDamage = multiParse({"mortardamage", "do"}, parsePercentOrNumber, 20, 0)
773 beeDamage = multiParse({"beedamage", "dx"}, parsePercentOrNumber, 50, 0)
774 dynamiteDamage = multiParse({"dynamitedamage", "d5"}, parsePercentOrNumber, 75, 0)
775 cakeDamage = multiParse({"cakedamage", "dc"}, parsePercentOrNumber, 75, 0)
776 batDamage = multiParse({"batdamage", "dB"}, parsePercentOrNumber, 30, 0)
777 shoryukenDamage = multiParse({"shoryukendamage", "dF"}, parsePercentOrNumber, 30, 0)
778 whipDamage = multiParse({"whipdamage", "dw"}, parsePercentOrNumber, 30, 0)
779 rcPlaneDamage = multiParse({"rcplanedamage", "dr"}, parsePercentOrNumber, 25, 0)
780 cleaverDamage = multiParse({"cleaverdamage", "dv"}, parsePercentOrNumber, 40000, 0)
781 eggDamage = multiParse({"eggdamage", "dE"}, parsePercentOrNumber, 10, 0)
782 pianoDamage = multiParse({"pianodamage", "dp"}, parsePercentOrNumber, 80, 0)
783 limburgerDamage = multiParse({"limburgerdamage", "di"}, parsePercentOrNumber, 20, 0)
784 deagleDamage = multiParse({"deagledamage", "dd"}, parsePercentOrNumber, 7, 0)
785 shotgunDamage = multiParse({"shotgundamage", "dO"}, parsePercentOrNumber, 25, 0)
786 sniperDamage = multiParse({"sniperdamage", "ds"}, parsePercentOrNumber, 100000, 0)
787 sineDamage = multiParse({"sinedamage", "dS"}, parsePercentOrNumber, 35, 0)
788 kamikazeDamage = multiParse({"kamikazedamage", "dK"}, parsePercentOrNumber, 30, 0)
789 airBombDamage = multiParse({"airbombdamage", "dA"}, parsePercentOrNumber, 30, 0)
790 drillRocketDamage = multiParse({"drillrocketdamage", "d3"}, parsePercentOrNumber, 50, 0)
791 drillStrikeDrillDamage = multiParse({"drillstrikedrilldamage", "dD"}, parsePercentOrNumber, 30, 0)
792 blowTorchDamage = multiParse({"blowtorchdamage", "dT"}, parsePercentOrNumber, 2, 0)
793 pickHammerDamage = multiParse({"pickhammerdamage", "dI"}, parsePercentOrNumber, 6, 0)
794 hammerDamage = multiParse({"hammerdamage", "dj"}, parseInt, 1)
795 hammerExtraDamage = multiParse({"hammerextradamage", "dJ"}, parseInt, 1)
796 flameDamage = multiParse({"flamedamage", "df"}, parsePercentOrNumber, 2, 0)
797 hogDamage = multiParse({"hogdamage", "dh"}, parsePercentOrNumber, 30, 0)
798 crateDamage = multiParse({"cratedamage", "dC"}, parsePercentOrNumber, 25, 0)
799 barrelDamage = multiParse({"barreldamage", "de"}, parsePercentOrNumber, 75, 0)
801 -- Misc.
802 mudballPush = multiParse({"mudballstrength", "wm"}, parsePercentOrNumber, 200000, 0)
803 sineStrength = multiParse({"sinestrength", "wS"}, parseInt, 1)
804 resurrectorRange = multiParse({"resurrectorrange", "9"}, parsePercentOrNumber, 100)
805 seductionRange = multiParse({"seductionrange", "7"}, parsePercentOrNumber, 250)
806 ropeOpacity = multiParse({"ropeopacity", "xR"}, parsePercentOrNumber, 255, 0, 255)
807 if params["ropecolor"] == "clan" or params["xr"] == "clan" then
808 ropeColor = "clan"
809 else
810 ropeColor = multiParse({"ropecolor", "xr"}, parseHex, 0x0, 0xFFFFFF)
813 buildRange = multiParse({"buildrange", "J"}, parsePercentOrNumber, 256, 1)
816 function onGameInit()
817 -- Build desciption text
818 local goalArray = {}
820 -- Activate the hedgepot
821 local hpModWeaps, hpTexts
822 if hedgePot == true then
823 hpModWeaps, hpTexts = hedgepot()
824 -- Text comes later
828 -- Most significant game hacks first
829 if gravity ~= nil and gravity ~= 100 then
830 if gravity > 100 then
831 table.insert(goalArray, string.format(loc("High gravity: Base gravity is %d%%."), math.floor(gravity)))
832 else
833 table.insert(goalArray, string.format(loc("Low gravity: Base gravity is %d%%."), math.floor(gravity)))
835 DisableGameFlags(gfLowGravity)
837 if strategicTools == true then
838 table.insert(goalArray, loc("Strategic tools: Girders, rubbers, dirt balls, land sprays and resurrections end your turn."))
839 DisableGameFlags(gfInfAttack)
841 if buildLimit == 1 then
842 table.insert(goalArray, string.format(loc("Slow builder: You may use only one construction, rubber or land spray per turn."), buildLimit))
843 elseif buildLimit ~= nil then
844 table.insert(goalArray, string.format(loc("Slow builder: You may use construction, rubber and land spray up to %d time(s) per turn."), buildLimit))
846 if maxPower == true then
847 table.insert(goalArray, loc("Maximum launching power: Weapons will always be fired with full power."))
849 if buildRange == "inf" then
850 if SetMaxBuildDistance ~= nil then
851 SetMaxBuildDistance(0)
853 table.insert(goalArray, loc("Construction site: Girders and rubbers can be placed without range limits."))
855 if noJumping == true then
856 table.insert(goalArray, loc("No jumping: Hedgehogs can't jump."))
858 if noKnock == true then
859 table.insert(goalArray, loc("Hogs of Steel: Hedgehogs won't be pushed away by sliding hedgehogs."))
861 if hogFriction ~= nil then
862 if hogFriction >= 9989 then
863 table.insert(goalArray, loc("Extremely sticky hedgehogs: Hedgehogs won't slide at all."))
864 elseif hogFriction > 0 then
865 table.insert(goalArray, loc("Sticky hedgehogs: Hedgehog won't slide as much."))
866 elseif hogFriction < 0 then
867 table.insert(goalArray, loc("Slippery hedgehogs: Hedgehog will slide more easily, watch your step!"))
871 if sturdyCrates == true then
872 table.insert(goalArray, loc("Sturdy crates: Crates can not be destroyed."))
874 if teamCure == true then
875 table.insert(goalArray, loc("TeamCure™: Health crates cure all team mates of poison."))
877 if shareHealth == true then
878 table.insert(goalArray, loc("Health Socialism: Health in crates is shared among team comrades."))
880 if epidemic == true then
881 table.insert(goalArray, loc("Epidemic: Poisoned hedgehogs infect others by touching."))
883 if poisonKills == true then
884 table.insert(goalArray, loc("Lethal poison: Poison can reduce the health to 0."))
886 if airFlamesHurt == true then
887 table.insert(goalArray, loc("Dangerous falling flames: Flames in mid-air can hurt hedgehogs."))
889 if stickyFlames == "always" then
890 table.insert(goalArray, loc("Long-lived fire: All flames keep burning between turns."))
891 elseif stickyFlames == "never" then
892 table.insert(goalArray, loc("Short-lived fire: All flames quickly burn out and do not keep burning between turns."))
894 if donorBox == true then
895 if GetGameFlag(gfKing) then
896 table.insert(goalArray, loc("Donor boxes: A dying king leaves a box with all the hog's weapons inside."))
897 else
898 table.insert(goalArray, loc("Donor boxes: The last dying hog of a team leaves a box with all the hog's weapons inside."))
902 -- Weapon/utiliy tweaks later
904 -- Mines timer
905 -- We set MinesTime to 3000 to suppress the engine's message
906 if mineTimerMin == 0 and mineTimerMax == 0 then
907 table.insert(goalArray, loc("Mine timer: Mines explode instantly."))
908 MinesTime = 3000
909 elseif mineTimerMin ~= mineTimerMax then
910 local str
911 if(mineTimerMin % 1000 == 0 and mineTimerMax % 1000 == 0) then
912 str = string.format(loc("Mine timer: Mines explode after %.0f-%.0f seconds."), mineTimerMin/1000, mineTimerMax/1000)
913 else
914 str = string.format(loc("Mine timer: Mines explode after %.1f-%.1f seconds."), mineTimerMin/1000, mineTimerMax/1000)
916 table.insert(goalArray, str)
917 MinesTime = 3000
918 elseif mineTimerMin ~= nil then
919 local str
920 if(mineTimerMin % 1000 == 0 and mineTimerMax % 1000 == 0) then
921 str = string.format(loc("Mine timer: Mines explode after %.0f second(s)."), mineTimerMin/1000)
922 else
923 str = string.format(loc("Mine timer: Mines explode after %.1f second(s)."), mineTimerMin/1000)
925 table.insert(goalArray, str)
926 MinesTime = 3000
928 if stickyMineTimerMin == 0 and stickyMineTimerMax == 0 then
929 table.insert(goalArray, loc("Sticky mine timer: Sticky mines explode instantly."))
930 elseif stickyMineTimerMin ~= stickyMineTimerMax then
931 local str
932 if(stickyMineTimerMin % 1000 == 0 and stickyMineTimerMax % 1000 == 0) then
933 str = string.format(loc("Sticky mine timer: Sticky mines explode after %.0f-%.0f seconds."), stickyMineTimerMin/1000, stickyMineTimerMax/1000)
934 else
935 str = string.format(loc("Sticky mine timer: Sticky mines explode after %.1f-%.1f seconds."), stickyMineTimerMin/1000, stickyMineTimerMax/1000)
937 table.insert(goalArray, str)
938 elseif stickyMineTimerMin ~= nil then
939 local str
940 if(stickyMineTimerMin % 1000 == 0 and stickyMineTimerMax % 1000 == 0) then
941 str = string.format(loc("Sticky mine timer: Sticky mines explode after %.0f second(s)."), stickyMineTimerMin/1000)
942 else
943 str = string.format(loc("Sticky mine timer: Sticky mines explode after %.1f second(s)."), stickyMineTimerMin/1000)
945 table.insert(goalArray, str)
947 if airMineTimerMin == 0 and airMineTimerMax == 0 then
948 table.insert(goalArray, loc("Air mine timer: Air mines explode instantly."))
949 elseif airMineTimerMin ~= airMineTimerMax then
950 local str
951 if(airMineTimerMin % 1000 == 0 and airMineTimerMax % 1000 == 0) then
952 str = string.format(loc("Air mine timer: Air mines explode after %.0f-%.0f seconds."), airMineTimerMin/1000, airMineTimerMax/1000)
953 else
954 str = string.format(loc("Air mine timer: Air mines explode after %.1f-%.1f seconds."), airMineTimerMin/1000, airMineTimerMax/1000)
956 table.insert(goalArray, str)
957 elseif airMineTimerMin ~= nil then
958 local str
959 if(airMineTimerMin % 1000 == 0 and airMineTimerMax % 1000 == 0) then
960 str = string.format(loc("Air mine timer: Air mines explode after %.0f second(s)."), airMineTimerMin/1000)
961 else
962 str = string.format(loc("Air mine timer: Air mines explode after %.1f second(s)."), airMineTimerMin/1000)
964 table.insert(goalArray, str)
966 if kamikazeTrigger then
967 table.insert(goalArray, loc("Kamikaze Pro: Kamikaze can be detonated early with the attack key."))
969 if saucerFuel == "inf" and birdyEnergy == "inf" and rcPlaneTimer == "inf" then
970 table.insert(goalArray, loc("Free flying: Unlimited fuel or flight time with flying saucer, Birdy and RC plane."))
971 else
972 if saucerFuel == "inf" then
973 table.insert(goalArray, loc("Endless saucer fuel: Flying saucers never run out of fuel."))
974 elseif saucerFuel ~= nil and saucerFuel ~= 2000 then
975 table.insert(goalArray, string.format(loc("Flying saucer fuel: The flying saucer has %d%% fuel."), round(saucerFuel/20)))
977 if birdyEnergy == "inf" then
978 table.insert(goalArray, loc("Badass Birdy: Birdy can fly forever."))
979 elseif birdyEnergy ~= nil then
980 if birdyEnergy > 2000 then
981 table.insert(goalArray, string.format(loc("Energetic Birdy: Birdy has %d%% stamina."), round(birdyEnergy/20)))
982 elseif birdyEnergy < 2000 then
983 table.insert(goalArray, string.format(loc("Weak Birdy: Birdy has %d%% stamina."), round(birdyEnergy/20)))
986 if rcPlaneTimer == "inf" then
987 table.insert(goalArray, loc("Endless RC plane fuel: RC planes never run out of fuel."))
988 elseif rcPlaneTimer ~= nil and rcPlaneTimer ~= 15000 then
989 table.insert(goalArray, string.format(loc("RC plane fuel: RC planes can fly for %d second(s)."), round(rcPlaneTimer/1000)))
992 if lowGravity ~= nil and lowGravity ~= 50 then
993 if lowGravity <= 100 then
995 table.insert(goalArray, string.format(loc("Modified low gravity: Low gravity utility modifies the gravity to %d%% of the base gravity."), math.floor(lowGravity)))
996 else
997 table.insert(goalArray, string.format(loc("High gravity utility: Low gravity utility modifies the gravity to %d%% of the base gravity."), math.floor(lowGravity)))
999 DisableGameFlags(gfLowGravity)
1001 if extraTime ~= nil and extraTime ~= 30000 then
1002 table.insert(goalArray, string.format(loc("Modified extra time: Using extra time gives you %.0f second(s)."), extraTime/1000))
1005 -- Hedgepot: Random modified property ofbweapon/utility property
1006 if hedgePot and hpModWeaps > 0 then
1007 -- Assumes that only 1 weapon/utility is changed
1008 table.insert(goalArray, string.format(loc("Surprise supplies: A random property was changed: %s"), hpTexts[1]))
1013 -- Engine flags come at last
1014 if GetGameFlag(gfMoreWind) then
1015 -- Add a goal message for this game modifier because it was forgotten in engine
1016 -- FIXME: Remove this as soon the engine has fixed it!
1017 table.insert(goalArray, string.format(loc("Crazy wind: Almost everything is influenced by wind.")))
1020 -- Concatenate the entire array for the final Goals string
1021 if #goalArray > 0 then
1022 local goalString = ""
1023 for i=1,#goalArray do
1024 goalString = goalString .. goalArray[i] .. "|"
1026 Goals = goalString
1029 -- Misc. setup (no messages)
1030 if ready ~= nil then
1031 Ready = ready
1033 if shoppaBorder == true then
1034 EnableGameFlags(gfShoppaBorder)
1036 if fairWind == true or maxWind ~= nil then
1037 -- Disable engine's wind because we want to do it our own way
1038 EnableGameFlags(gfDisableWind)
1041 -- Set gravity
1042 if gravity ~= nil then
1043 SetGravity(gravity)
1046 trackTeams()
1049 function onGearAdd(gear)
1050 local gt = GetGearType(gear)
1052 -- Gear hacks
1053 if gt == gtHedgehog then
1054 trackGear(gear)
1055 if epidemic then
1056 hogGears[gear] = true
1059 local teamName = GetHogTeamName(gear)
1060 if hogNumbers[teamName] == nil then
1061 -- first hog
1062 hogNumbers[teamName] = { [1] = gear }
1063 else
1064 table.insert(hogNumbers[teamName], gear)
1066 if teams[teamName] == nil then
1067 teams[teamName] = 1
1068 else
1069 teams[teamName] = teams[teamName] + 1
1072 if poison == true then
1073 local dmg
1074 if poisonDamage == nil then
1075 dmg = 5
1076 else
1077 dmg = poisonDamage
1079 SetEffect(gear, hePoisoned, dmg)
1082 if noKnocking then
1083 disableKnocking(gear)
1085 if hogFriction ~= nil then
1086 -- This assumes a default friction of 9989
1087 local newFriction = GetGearFriction(gear) - hogFriction
1088 SetGearFriction(gear, newFriction)
1090 if hogDamage ~= nil then
1091 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hogDamage)
1094 if gt == gtCase then
1095 if crateDamage ~= nil then
1096 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, crateDamage)
1098 -- Random health case health
1099 if healthCaseAmountMin ~= nil and healthCaseAmountMax ~= nil then
1100 table.insert(healthCaseGearsTodo, gear)
1102 if sturdyCrates then
1103 SetState(gear, bor(GetState(gear), gstNoDamage))
1106 if gt == gtExplosives then
1107 if barrelHealth ~= nil then
1108 SetHealth(gear, barrelHealth)
1109 if barrelHealth > 60 then
1110 SetState(gear, bor(GetState(gear), gstFrozen))
1113 if barrelDamage ~= nil then
1114 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, barrelDamage)
1117 if gt == gtShell then
1118 if bazookaDamage ~= nil then
1119 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, bazookaDamage)
1122 if gt == gtMortar then
1123 if mortarDamage ~= nil then
1124 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mortarDamage)
1127 if gt == gtGrenade then
1128 if grenadeDamage ~= nil then
1129 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, grenadeDamage)
1132 if gt == gtClusterBomb then
1133 if clusterBombDamage ~= nil then
1134 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, clusterBombDamage)
1137 if gt == gtCluster then
1138 if clusterDamage ~= nil then
1139 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, clusterDamage)
1142 if gt == gtMine then
1143 mineGears[gear] = true
1144 if mineTimerMin ~= nil then
1145 if mineTimerMin ~= mineTimerMax then
1146 SetTimer(gear, mineTimerMin + GetRandom(mineTimerMax-mineTimerMin+1))
1147 else
1148 SetTimer(gear, mineTimerMin)
1151 if mineDamage ~= nil then
1152 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mineDamage)
1155 if gt == gtSMine then
1156 if stickyMineTimerMin ~= nil then
1157 if stickyMineTimerMin ~= stickyMineTimerMax then
1158 SetTimer(gear, stickyMineTimerMin + GetRandom(stickyMineTimerMax-stickyMineTimerMin+1))
1159 else
1160 SetTimer(gear, stickyMineTimerMin)
1162 elseif stickyMineTimer ~= nil then
1163 SetTimer(gear, stickyMineTimer)
1165 if stickyMineDamage ~= nil then
1166 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, stickyMineDamage)
1169 if gt == gtAirMine then
1170 if airMineTimerMin ~= nil then
1171 local t
1172 if airMineTimerMin ~= airMineTimerMax then
1173 t = airMineTimerMin + GetRandom(airMineTimerMax-airMineTimerMin+1)
1174 else
1175 t = airMineTimerMin
1177 SetTimer(gear, t)
1178 SetGearValues(gear, nil, nil, t) -- WDTimer
1180 if airMineSeekSpeed ~= nil or airMineSeekRange ~= nil then
1181 local r = airMineSeekRange
1182 if airMineSeekRange == "inf" then
1183 r = 0xFFFFFFFF
1185 SetGearValues(gear, r, airMineSeekSpeed)
1187 if airMineFriction ~= nil then
1188 SetGearPos(gear, airMineFriction)
1190 if airMineDamage ~= nil then
1191 SetGearValues(gear, nil, nil, nil, nil, nil, airMineDamage) -- Karma
1194 if gt == gtJetpack then
1195 saucerGear = gear
1196 if type(saucerFuel) == "number" then
1197 SetHealth(gear, saucerFuel)
1200 if gt == gtIceGun then
1201 if freezerFuel ~= nil then
1202 SetHealth(gear, freezerFuel)
1205 if gt == gtFlamethrower then
1206 if flamethrowerFuel ~= nil then
1207 SetHealth(gear, flamethrowerFuel)
1210 if gt == gtLandGun then
1211 if buildLimit ~= nil then
1212 if stuffBuilt >= buildLimit then
1213 -- Destroy land gun immediately if construction limit is exceeded
1214 PlaySound(sndDenied)
1215 AddCaption(loc("Building limit exceeded!"), 0xFFFFFFFF, capgrpAmmostate)
1216 SetHealth(gear, 0)
1217 -- This may have reduced ammo by one, so we have to return it
1218 local ammo = GetAmmoCount(CurrentHedgehog, amLandGun)
1219 if(ammo ~= 100) then
1220 AddAmmo(CurrentHedgehog, amLandGun, ammo + 1)
1222 else
1223 stuffBuilt = stuffBuilt + 1
1224 AddCaption(string.format(loc("Buildings left: %d"), buildLimit - stuffBuilt), 0xFFFFFFFF, capgrpAmmostate)
1227 if landsprayFuel ~= nil then
1228 SetHealth(gear, landsprayFuel)
1231 if gt == gtRCPlane then
1232 rcPlaneGear = gear
1233 if type(rcPlaneTimer) == "number" then
1234 SetTimer(gear, rcPlaneTimer)
1236 if rcPlaneBombs == "inf" then
1237 -- Set RC plane bombs to a arbitrarily large number, but later it will be ensured this stays always above 0
1238 SetHealth(gear, 9001)
1239 elseif rcPlaneBombs ~= nil then
1240 SetHealth(gear, rcPlaneBombs)
1242 if rcPlaneDamage ~= nil then
1243 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, rcPlaneDamage)
1246 if gt == gtAirBomb then
1247 if airBombDamage ~= nil then
1248 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, airBombDamage)
1251 if gt == gtBirdy then
1252 birdyGear = gear
1253 if type(birdyEnergy) == "number" then
1254 SetHealth(gear, birdyEnergy)
1256 if birdyEggs == "inf" then
1257 -- Set eggs to a arbitrarily large number, but later it will be ensured this stays always above 0
1258 SetFlightTime(gear, 9001)
1259 elseif birdyEggs ~= nil then
1260 SetFlightTime(gear, birdyEggs)
1263 if gt == gtEgg then
1264 if eggDamage ~= nil then
1265 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, eggDamage)
1268 if gt == gtPiano then
1269 if pianoBounces ~= nil then
1270 SetGearPos(gear, 5 - pianoBounces)
1272 if pianoDamage ~= nil then
1273 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, pianoDamage)
1276 if gt == gtAirAttack then
1277 planeGears[gear] = true
1278 local planeType = GetState(gear)
1279 if planeType == 0 then
1280 -- air attack
1281 if airAttackBombs ~= nil then
1282 SetHealth(gear, airAttackBombs)
1284 if airAttackGap ~= nil then
1285 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, airAttackGap)
1287 elseif planeType == 1 then
1288 -- mine strike
1289 if mineStrikeMines ~= nil then
1290 SetHealth(gear, mineStrikeMines)
1292 if mineStrikeGap ~= nil then
1293 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mineStrikeGap)
1295 elseif planeType == 2 then
1296 -- napalm
1297 if napalmBombs ~= nil then
1298 SetHealth(gear, napalmBombs)
1300 if napalmGap ~= nil then
1301 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, napalmGap)
1303 elseif planeType == 3 then
1304 -- drill strike
1305 if drillStrikeDrills ~= nil then
1306 SetHealth(gear, drillStrikeDrills)
1308 if drillStrikeGap ~= nil then
1309 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, drillStrikeGap)
1313 if gt == gtNapalmBomb and napalmBombTimer ~= nil then
1314 SetTimer(gear, napalmBombTimer)
1316 if gt == gtBallGun and ballGunBalls ~= nil then
1317 SetTimer(gear, ballGunBalls * 100 - 99)
1319 if gt == gtBall then
1320 if ballTimer ~= nil then
1321 local timer
1322 if type(ballTimer) == "number" then
1323 timer = ballTimer
1324 elseif ballTimer == "manual" then
1325 if manualTimer == nil then
1326 manualTimer = 5
1328 timer = manualTimer*1000
1330 SetTimer(gear, timer)
1332 if ballDamage ~= nil then
1333 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, ballDamage)
1336 if gt == gtSniperRifleShot then
1337 if sniperStrength ~= nil then
1338 SetHealth(gear, sniperStrength)
1340 if sniperDamage ~= nil then
1341 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, sniperDamage)
1344 if gt == gtDEagleShot then
1345 if deagleStrength ~= nil then
1346 SetHealth(gear, deagleStrength)
1348 if deagleDamage ~= nil then
1349 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, deagleDamage)
1352 if gt == gtShotgunShot then
1353 if shotgunDamage ~= nil then
1354 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, shotgunDamage)
1357 if gt == gtHellishBomb then
1358 if hhgTimer ~= nil then
1359 local timer
1360 if type(hhgTimer) == "number" then
1361 timer = hhgTimer
1362 elseif hhgTimer == "manual" then
1363 if manualTimer == nil then
1364 manualTimer = 5
1366 timer = manualTimer*1000
1368 SetTimer(gear, timer)
1370 if hhgDamage ~= nil then
1371 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hhgDamage)
1374 if gt == gtWatermelon then
1375 if melonDamage ~= nil then
1376 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, melonDamage)
1379 if gt == gtMelonPiece then
1380 if melonPieceDamage ~= nil then
1381 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, melonPieceDamage)
1384 if gt == gtDynamite then
1385 if dynamiteTimer ~= nil then
1386 local timer
1387 if type(dynamiteTimer) == "number" then
1388 timer = dynamiteTimer
1389 elseif dynamiteTimer == "manual" then
1390 if manualTimer == nil then
1391 manualTimer = 5
1393 timer = manualTimer*1000
1396 SetTimer(gear, timer)
1397 if timer > 10000 then
1398 SetTag(gear, 32)
1399 elseif timer > 5000 then
1400 SetTag(gear, 32 + div(10000-timer, 166))
1401 else
1402 SetTag(gear, div(5000-timer, 166))
1405 if dynamiteDamage ~= nil then
1406 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, dynamiteDamage)
1408 dynaGears[gear] = true
1410 if gt == gtGasBomb then
1411 if limburgerDamage ~= nil then
1412 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, limburgerDamage)
1415 if gt == gtPoisonCloud and poisonCloudTimer ~= nil then
1416 if poisonCloudTimer == 0 then
1417 DeleteGear(gear)
1418 else
1419 SetTimer(gear, poisonCloudTimer)
1422 if gt == gtSineGunShot then
1423 if sineStrength ~= nil then
1424 SetGearValues(gear, nil, nil, nil, sineStrength)
1426 if sineDamage ~= nil then
1427 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, sineDamage)
1430 if gt == gtResurrector and resurrectorRange ~= nil then
1431 SetGearValues(gear, nil, nil, nil, resurrectorRange)
1433 if gt == gtSeduction and seductionRange ~= nil then
1434 SetGearValues(gear, nil, nil, nil, seductionRange)
1436 if gt == gtBee then
1437 if beeTimer1 ~= nil then
1438 SetTimer(gear, beeTimer1)
1440 if beeTimer1 ~= nil or beeTimer2 ~= nil then
1441 SetGearPos(gear, 0)
1443 if beeDamage ~= nil then
1444 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, beeDamage)
1446 beeGears[gear] = true
1448 if gt == gtDrill then
1449 -- Is this a normal drill (not from drill strike)?
1450 if band(GetState(gear), gsttmpFlag) == 0 then
1451 if drillRocketTimer ~= nil then
1452 local timer
1453 if type(drillRocketTimer) == "number" then
1454 timer = drillRocketTimer
1455 elseif drillRocketTimer == "manual" then
1456 if manualTimer == nil then
1457 manualTimer = 5
1459 timer = manualTimer*1000
1461 SetTimer(gear, timer)
1463 if drillRocketDamage ~= nil then
1464 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, drillRocketDamage)
1466 else
1467 if drillStrikeDrillDamage ~= nil then
1468 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, drillStrikeDrillDamage)
1472 if gt == gtCake then
1473 if cakeTimer ~= nil then
1474 SetHealth(gear, cakeTimer)
1476 if cakeDamage ~= nil then
1477 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, cakeDamage)
1480 if gt == gtFirePunch then
1481 if shoryukenDamage ~= nil then
1482 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, shoryukenDamage)
1485 if gt == gtWhip then
1486 if whipDamage ~= nil then
1487 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, whipDamage)
1490 if gt == gtShover then
1491 if batDamage ~= nil then
1492 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, batDamage)
1495 if gt == gtKamikaze then
1496 kamikazeGear = gear
1497 if kamikazeRange ~= nil then
1498 SetHealth(gear, kamikazeRange)
1500 if kamikazeDamage ~= nil then
1501 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, kamikazeDamage)
1504 if gt == gtKnife then
1505 if cleaverDamage ~= nil then
1506 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, cleaverDamage)
1509 if gt == gtHammer then
1510 -- Hammer damage
1511 if(extraDamageUsed and hammerExtraDamage ~= nil) then
1512 -- Hammer with Extra Damage
1513 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hammerExtraDamage)
1514 elseif((not extraDamageUsed) and (hammerDamage ~= nil)) then
1515 -- Hammer without Extra Damage
1516 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hammerDamage)
1519 if gt == gtHammerHit and hammerStrength ~= nil then
1520 if hammerStrength == "inf" then
1521 SetTimer(gear, 0)
1522 else
1523 SetTimer(gear, hammerStrength)
1526 if gt == gtPickHammer then
1527 if pickHammerTimer == "inf" then
1528 SetTimer(gear, 0)
1529 elseif pickHammerTimer ~= nil then
1530 SetTimer(gear, pickHammerTimer)
1532 if pickHammerDamage ~= nil then
1533 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, pickHammerDamage)
1535 -- Disallow pickhammer to be moved left and right
1536 if pickHammerStraightDown == true then
1537 SetGearMessage(CurrentHedgehog, band(GetGearMessage(CurrentHedgehog), bnot(gmLeft+gmRight)))
1538 -- Input mask will be restored when pick hammer is deleted
1539 SetInputMask(band(GetInputMask(), bnot(gmLeft+gmRight)))
1542 if gt == gtBlowTorch then
1543 if blowTorchTimer == "inf" then
1544 SetTimer(gear, 0)
1545 elseif blowTorchTimer ~= nil then
1546 SetTimer(gear, blowTorchTimer)
1548 if blowTorchDamage ~= nil then
1549 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, blowTorchDamage)
1552 if gt == gtGrave and donorBox then
1553 flaggedGrave = gear
1555 if gt == gtGirder then
1556 local ignore = false
1557 if buildLimit ~= nil then
1558 if stuffBuilt >= buildLimit then
1559 AddCaption(loc("Building limit exceeded!"), 0xFFFFFFFF, capgrpAmmostate)
1560 DeleteGear(gear)
1561 ignore = true
1564 if not ignore then
1565 lastConstruction = gear
1568 if gt == gtFlake and noSnow == true then
1569 -- Get rid of snow flakes
1570 -- But only replace snow flakes, not the flakes from land spray!
1571 if band(GetState(gear), gsttmpFlag) == 0 then
1572 -- Replace snow flake gear with (harmless) visual gear flake
1573 local x, y = GetGearPosition(gear)
1574 local dx, dy = GetGearVelocity(gear)
1575 DeleteGear(gear)
1576 AddVisualGear(x, y, vgtFlake, 0, false)
1579 if gt == gtRope then
1580 -- Rope color and rope opacity
1581 if ropeColor ~= nil or ropeOpacity ~= nil then
1582 -- Mostly just some bitmask stuff (color is in RGBA format)
1583 local opacityMask
1584 if ropeOpacity ~= nil then
1585 opacityMask = bor(0xFFFFFF00, ropeOpacity)
1586 else
1587 opacityMask = 0xFFFFFFFF
1589 local actualColor
1590 if ropeColor == "clan" then
1591 actualColor = band(GetClanColor(GetHogClan(CurrentHedgehog)), opacityMask)
1592 elseif ropeColor ~= nil then
1593 actualColor = band(ropeColor*0x100+0xFF, opacityMask)
1594 else
1595 actualColor = band(0xD8D8D8FF, opacityMask)
1598 -- Set rope to “line” mode (needed for coloization to work)
1599 SetTag(gear, 1)
1600 -- Set rope color
1601 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, actualColor)
1603 ropeGear = gear
1605 if gt == gtTardis then
1606 if tardisReturnTurns ~= nil then
1607 -- Remember the TimeBox' gear ID for later use
1608 tardisGears[gear] = tardisReturnTurns
1611 if gt == gtSnowball then
1612 if strategicTools then
1613 -- Strategic tools: Snowball
1614 strategicToolsEndTurn(3000, false)
1616 if mudballPush ~= nil then
1617 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mudballPush)
1621 if gt == gtFlame then
1622 -- Mid-air flames hurt hedgehogs
1623 if airFlamesHurt == true then
1624 SetFlightTime(gear, 0)
1626 -- Make all flames sticky
1627 if stickyFlames == "always" then
1628 SetState(gear, bor(GetState(gear), gsttmpFlag))
1629 -- Make all flames non-sticky
1630 elseif stickyFlames == "never" then
1631 SetState(gear, band(GetState(gear), bnot(gsttmpFlag)))
1633 if flameDamage ~= nil then
1634 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, flameDamage)
1638 -- No jumping mode
1639 if noJumping and (gt == gtJetpack or gt == gtRope or gt == gtParachute) then
1640 specialJumpingGear = gear
1641 SetInputMask(bor(GetInputMask(), gmLJump))
1642 SetInputMask(band(GetInputMask(), bnot(gmHJump)))
1646 function onHogRestore(gear)
1647 if epidemic then
1648 hogGears[gear] = true
1652 function onGearDelete(gear)
1653 local gt = GetGearType(gear)
1655 -- Clear temp. gear variables
1656 if gt == gtHedgehog then
1657 hogGears[gear] = nil
1658 elseif gt == gtBirdy then
1659 birdyGear = nil
1660 elseif gt == gtRCPlane then
1661 rcPlaneGear = nil
1662 elseif gt == gtJetpack then
1663 saucerGear = nil
1664 elseif gt == gtDynamite then
1665 dynaGears[gear] = nil
1666 elseif gt == gtRope then
1667 ropeGear = nil
1668 elseif gt == gtAirAttack then
1669 planeGears[gear] = nil
1670 elseif gt == gtTardis then
1671 tardisGears[gear] = nil
1672 elseif gt == gtMine then
1673 mineGears[gear] = nil
1676 if gt == gtHedgehog then
1677 if donorBox and donors[gear] then
1678 if flaggedGrave ~= nil then
1679 DeleteGear(flaggedGrave)
1680 flaggedGrave = nil
1682 local x, y = GetGearPosition(gear)
1684 local ammos = {}
1685 local ammoTypes = {}
1687 --[[ NOTE: This is a hack to get the number of available
1688 ammo types in Hedgewars.
1689 TODO: Update this code whenever ammo types are extended,
1690 or when the engine allows to query this number directly
1691 somehow.
1693 local maxAmmoID = 58 -- Ammo count in 0.9.22
1694 for i=1,maxAmmoID do
1695 ammoTypes[i] = i-1
1697 for i=1,#ammoTypes do
1698 if ammoTypes[i] ~= amSkip then
1699 local count = GetAmmoCount(gear, ammoTypes[i])
1700 if count > 0 then
1701 ammos[ammoTypes[i]] = count
1706 local box = SpawnFakeAmmoCrate(x, y, false, false)
1707 donorBoxes[box] = ammos
1709 trackDeletion(gear)
1712 if gt == gtBee and (beeTimer1 ~= nil or beeTimer2 ~= nil) then
1713 beeGears[gear] = nil
1716 -- Collect donor crate and award its contents to the collector
1717 if donorBox and gt == gtCase and donorBoxes[gear] ~= nil then
1718 if band(GetGearMessage(gear), gmDestroy) ~= 0 then
1719 for ammoType, addCount in pairs(donorBoxes[gear]) do
1720 modAmmo(CurrentHedgehog, ammoType, addCount)
1722 PlaySound(sndShotgunReload)
1723 AddCaption(loc("Donor box collected!"), GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmoinfo)
1724 donorBoxes[gear] = nil
1728 -- Health Crate stuff
1729 if (teamCure or shareHealth) and gt == gtCase and band(GetGearPos(gear), 0x2) ~= 0 then
1730 if band(GetGearMessage(gear), gmDestroy) ~= 0 then
1731 -- TeamCure™
1732 if teamCure then
1733 runOnHogsInTeam(function(hog)
1734 SetEffect(hog, hePoisoned, 0)
1735 end, GetHogTeamName(CurrentHedgehog))
1738 -- Health Socialism
1739 if shareHealth then
1740 --[[ Okay, here's how it works:
1741 The health pack is divided by the number of hogs in the
1742 team. The integer result of box health divided by hogs is then
1743 added to each member.
1744 If there is a remainder, it is split up among the hogs this
1745 way: We start counting from the current hedgehog, and give it
1746 another HP. We continue with the next hogs in team order and
1747 give them 1 HP each until the remainder is "used up".
1748 After this algorithm, the entire health pack has been used up.
1750 Examples:
1751 - 50 HP box, 5 team members: 50/10=10, so each gets 10 HP.
1752 - Hog 2 collects a 30 HP box, 4 team members:
1753 30/4 = 7, remainder 2. So:
1754 +8 HP for Hog 2 and Hog 3 (current and next), +7 HP for Hog 1 and Hog 4.
1755 - 1 HP, 8 team members: +1 HP only for current hog.
1759 -- REALLY complicated hack to find out the correct hog order
1760 local teamHogTable = {}
1761 local CurrentHedgehogID
1762 runOnHogsInTeam(function(hog)
1763 table.insert(teamHogTable, hog)
1764 if hog == CurrentHedgehog then
1765 CurrentHedgehogID = #teamHogTable
1767 end, GetHogTeamName(CurrentHedgehog))
1769 local newTeamHogTable = { CurrentHedgehog }
1770 for i=#teamHogTable, CurrentHedgehogID+1, -1 do
1771 table.insert(newTeamHogTable, teamHogTable[i])
1773 for i=1, CurrentHedgehogID-1 do
1774 table.insert(newTeamHogTable, teamHogTable[i])
1777 -- Now finally heal some hogs
1778 local boxHealth = GetHealth(gear)
1779 local healthPart = div(boxHealth, #teamHogTable)
1780 local remainder = math.fmod(boxHealth, #teamHogTable)
1782 for i=1, #newTeamHogTable do
1783 local heal
1784 local hog = newTeamHogTable[i]
1785 if i > remainder then
1786 heal = healthPart
1787 else
1788 heal = healthPart + 1
1790 SetHealth(hog, GetHealth(hog) + heal)
1793 -- Undo the original health crate effect
1794 SetHealth(CurrentHedgehog, GetHealth(CurrentHedgehog) - boxHealth)
1796 -- Overwrite health message
1797 AddCaption(string.format(loc("+%d (shared)"), boxHealth), GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmoinfo)
1802 -- No jumping mode
1803 if noJumping and (gt == gtJetpack or gt == gtRope or gt == gtParachute) then
1804 specialJumpingGear = nil
1805 SetInputMask(band(GetInputMask(), bnot(gmLJump + gmHJump)))
1808 -- Force pick hammer to dig straight down
1809 if pickHammerStraightDown and gt == gtPickHammer then
1810 -- Enable left and right again for pick hammer
1811 SetInputMask(bor(GetInputMask(), gmLeft+gmRight))
1814 if gt == gtKamikaze then
1815 kamikazeGear = nil
1818 -- Strategic tools: Resurrection and land spray
1819 if strategicTools then
1820 if gt == gtResurrector then
1821 strategicToolsEndTurn(3000, false)
1822 elseif gt == gtLandGun then
1823 strategicToolsEndTurn(4000, false)
1827 -- For some reason using blow torch enables knocking again
1828 if noKnock and gt == gtBlowTorch then
1829 runOnHogs(disableKnocking)
1833 function onHogHide(gear)
1834 hogGears[gear] = nil
1837 function onGameTick20()
1838 -- Reset saucer/rc plane fuel and birdy energy if infinite
1839 if rcPlaneTimer == "inf" and rcPlaneGear then
1840 SetTimer(rcPlaneGear, 15000)
1842 if saucerFuel == "inf" and saucerGear then
1843 -- TODO: Also prevent the fuel indicator from being shown
1844 SetHealth(saucerGear, 2000)
1846 if birdyEnergy == "inf" and birdyGear then
1847 SetHealth(birdyGear, 2000)
1850 -- Main bee timer
1851 if beeTimer2 ~= nil then
1852 for bee, _ in pairs(beeGears) do
1853 if GetGearPos(bee) == 0 then
1854 if GetTimer(bee) <= 20 then
1855 SetFlightTime(bee, GetTimer(bee))
1856 SetGearPos(bee, 1)
1858 elseif GetGearPos(bee) == 1 then
1859 SetTimer(bee, beeTimer2)
1860 SetGearPos(bee, 2)
1865 -- Dud mine health
1866 if dudMineHealth ~= nil then
1867 for mine, _ in pairs(mineGears) do
1868 if GetHealth(mine) == 0 then
1869 -- Dud mines explode when Damage reaches 35
1870 SetGearValues(mine, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 35 - dudMineHealth) -- Damage
1871 mineGears[mine] = nil
1876 -- Epidemic
1877 if epidemic then
1878 local tdist = 22 -- Touch distance
1879 for hog1, t1 in pairs(hogGears) do
1880 if GetEffect(hog1, hePoisoned) >= 1 and GetHealth(hog1) > 0 then
1881 local x1, y1 = GetGearPosition(hog1)
1882 for hog2, t2 in pairs(hogGears) do
1883 local x2, y2 = GetGearPosition(hog2)
1884 if GetEffect(hog2, hePoisoned) == 0 and GetEffect(hog2, heInvulnerable) == 0 and GetHealth(hog2) > 0 then
1885 if (x2 > x1 - tdist) and (x2 < x1 + tdist) and (y2 > y1 - tdist) and (y2 < y1 + tdist) and GetHealth(hog2) > 0 then
1886 SetEffect(hog2, hePoisoned, GetEffect(hog1, hePoisoned))
1894 -- Custom poison damage
1895 if poisonDamage ~= nil then
1896 runOnHogs(adjustPoisonDamage)
1899 -- Kill poisoned
1900 if poisonKills and not poisonKillDone then
1901 if TurnTimeLeft == 0 and CurrentHedgehog ~= nil then
1902 runOnHogs(doPoisonKill)
1903 poisonKillDone = true
1907 -- TimeBox timers
1908 if tardisReturnTurns ~= nil then
1909 for gear, value in pairs(tardisGears) do
1910 if GetGearPos(gear) == 4 then -- TimeBox is travelling through time
1911 if type(value) == "number" and value <= 0 then
1912 -- Special case: The turn counter is already at 0, so we can return immediately
1913 SetTimer(gear, 0)
1914 tardisGears[gear] = nil
1915 elseif value == "inf" then
1916 -- Constantly set the TimeBox timer to a high value to make sure it never reaches 0
1917 -- so we can manually trigger the return of the TimeBox
1918 SetTimer(gear, 1000000)
1924 -- Donor boxes
1925 if donorBox then
1926 runOnHogs(checkDonors)
1929 -- Show delayed message for modified weapon timers
1930 if setWeaponMessage then
1931 local curAmmo = GetCurAmmoType()
1932 if (curAmmo == amHellishBomb and hhgTimer == "manual")
1933 or (curAmmo == amDynamite and dynamiteTimer == "manual")
1934 or (curAmmo == amDrill and drillRocketTimer == "manual")
1935 or (curAmmo == amBallgun and ballTimer == "manual") then
1936 if manualTimer == nil then
1937 manualTimer = 5
1939 FakeAmmoinfoCaption(curAmmo)
1940 setWeaponMessage = nil
1944 -- Initial switch (Experimental!)
1945 if (CurrentHedgehog ~= nil and initialSwitch == true) then
1946 if (TurnTimeLeft > 0) and (TurnTimeLeft ~= TurnTime) and (switchStage < 5) then
1947 hogCounter = 0
1949 runOnHogsInTeam(countHog, GetHogTeamName(CurrentHedgehog) )
1951 if hogCounter > 1 then
1952 switchStage = switchStage + 1
1954 if switchStage == 1 then
1955 tmpSwitchCount = GetAmmoCount(CurrentHedgehog, amSwitch)
1956 AddAmmo(CurrentHedgehog, amSwitch, 100)
1957 elseif switchStage == 2 then
1958 SetWeapon(amSwitch)
1959 elseif switchStage == 3 then
1960 SetGearMessage(CurrentHedgehog, gmAttack)
1961 elseif switchStage == 4 then
1962 switchStage = 6
1963 AddAmmo(CurrentHedgehog, amSwitch, tmpSwitchCount)
1966 else
1967 switchStage = 6
1972 -- Random health case contents
1973 if healthCaseAmountMin ~= nil then
1974 for h=0, #healthCaseGearsTodo do
1975 local gear = healthCaseGearsTodo[h]
1976 if band(GetGearPos(gear), 0x2) ~= 0 then
1977 SetHealth(gear, healthCaseAmountMin + GetRandom(healthCaseAmountMax-healthCaseAmountMin+1))
1980 healthCaseGearsTodo = {}
1984 function onGameTick()
1985 if maxPower then
1986 if CurrentHedgehog ~= nil then
1987 if band(GetGearMessage(CurrentHedgehog), gmAttack) ~= 0 then
1988 local at = GetCurAmmoType()
1989 local ok = false
1990 -- Check if this weapon is power-based
1991 for i=1,#powerWeapons do
1992 if at == powerWeapons[i] then
1993 ok = true
1994 break
1997 if ok then
1998 -- Immediately set to full launching power and force-release the attack message in the next tick
1999 SetGearValues(CurrentHedgehog, nil, cMaxPower - 1)
2000 if releaseShotInNextTick then
2001 SetGearMessage(CurrentHedgehog, band(GetGearMessage(CurrentHedgehog), bnot(gmAttack)))
2002 releaseShotInNextTick = false
2003 else
2004 releaseShotInNextTick = true
2011 if strategicTools then
2012 if girderPlaced == true then
2013 girderPlaced = false
2014 strategicToolsEndTurn(4999, true)
2018 if donorBox then
2019 flaggedGrave = nil
2022 if dynamiteTimer ~= nil then
2023 for dynaGear, v in pairs(dynaGears) do
2024 if GetTimer(dynaGear) > 10000 then
2025 SetTag(dynaGear, 33)
2026 elseif GetTimer(dynaGear) == 5166 then
2027 SetTag(dynaGear, 0)
2032 -- Draw range circles for changed resurrector and seduction ranges
2033 -- TODO: Also remove the original circle as soon the engine permits it (original circles are currently (0.9.21-0.9.22) hardcoded)
2034 local dontDraw = false
2036 if band(GetState(CurrentHedgehog), gstWait+gstAttacking+gstAttacked+gstAnimation+gstHHJumping) == 0 and
2037 band(GetState(CurrentHedgehog), gstHHDriven) ~= 0 and
2038 band(GetGearMessage(CurrentHedgehog), gmLeft+gmRight) == 0 and
2039 ropeGear == nil then
2040 if resurrectorRange ~= nil and GetCurAmmoType() == amResurrector then
2041 if rangeCircle then
2042 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, resurrectorRange, 4, 0xFFFF80EE)
2043 else
2044 rangeCircle = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtCircle, 0, true)
2045 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, resurrectorRange, 4, 0xFFFF80EE)
2047 elseif seductionRange ~= nil and GetCurAmmoType() == amSeduction then
2048 if rangeCircle then
2049 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, seductionRange, 4, 0xFF8080EE)
2050 else
2051 rangeCircle = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtCircle, 0, true)
2052 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, seductionRange, 4, 0xFF8080EE)
2054 else
2055 dontDraw = true
2057 else
2058 dontDraw = true
2060 if dontDraw then
2061 if rangeCircle ~= nil then
2062 DeleteVisualGear(rangeCircle)
2064 rangeCircle = nil
2068 function onGameStart()
2070 -- Place random girders
2071 if girders ~= nil then
2072 local points = {}
2073 -- Will be set to true if we have exceeded the tryLimit once
2074 local dontBotherTrying = false
2075 local buffer = 160
2076 local tryLimit = 500
2077 --[[ The complexitiy of this code is O(n^2) but the algorithm may terminate early
2078 if it fails to validly place a girder ]]
2079 for i=1,girders do
2080 local x, y
2081 -- Try a couple of times to place a girder without being too close to other placed girders.
2082 local tries = 0
2083 while tries < tryLimit do
2084 -- Get a random girder position
2085 tries = tries + 1
2086 x, y = GetRandom(LAND_WIDTH-buffer)+buffer, GetRandom(LAND_HEIGHT-buffer)+buffer
2087 if dontBotherTrying then
2088 --[[ To reduce the complexity, we don't go through the next loop if we have once
2089 exceeded the tryLimit ]]
2090 break
2092 local placementOkay = true
2093 -- Check collisions with gears and land
2094 --[[ NOTE: Remove this line to make girders appear anywhere, even in land and near gears. In combination with
2095 EraseSprite, this can be used to create nice “girder holes”. The HWP file currently contains a legacy sprite
2096 for erasing (custom sprite 1).
2097 This MAY be a nice candidate for another parameter but it might be a bit glitchy ]]
2098 placementOkay = not TestRectForObstacle(x-div(buffer,2), y-div(buffer,2), x+div(buffer,2), y+div(buffer,2), false)
2099 -- Check whether this position it is too close to other girders
2100 if placementOkay then
2101 for p=1,#points do
2102 if math.sqrt(math.pow(math.abs(points[p].x - x), 2) + math.pow(math.abs(points[p].y -y), 2)) < buffer then
2103 placementOkay = false
2104 break
2108 if placementOkay then
2109 break
2112 if tries >= tryLimit then
2113 --[[ If we have once tried too many times, we stop adding girders unless
2114 forceLimits is false. DontBotherTrying is used to kinda reduce the algorithmic
2115 complexity for the following iterations if forceLimits is false ]]
2116 dontBotherTrying = true
2117 if forceLimits then
2118 break
2121 table.insert(points, {x=x, y=y})
2124 -- Place girders
2125 for i=1,#points do
2126 PlaceSprite(points[i].x, points[i].y, sprAmGirder, 0)
2130 -- David and Goliath
2131 if davidAndGoliath then
2132 for teamName, teamMembers in pairs(teams) do
2133 hogCounter = 0
2134 local goliath
2135 local healthForGoliath = 0
2136 runOnHogsInTeam(function(hog)
2137 -- Goliath
2138 if hogCounter == 0 then
2139 goliath = hog
2140 -- David
2141 else
2142 --[[ Remove half of the health (rounded down) ]]
2143 local removedHealth = div(GetHealth(hog), 2)
2144 healthForGoliath = healthForGoliath + removedHealth
2145 SetHealth(hog, GetHealth(hog) - removedHealth)
2147 hogCounter = hogCounter + 1
2148 end, teamName)
2150 -- Add all health to Goliath
2151 SetHealth(goliath, GetHealth(goliath) + healthForGoliath)
2156 function onHogAttack(ammoType)
2157 if ammoType == amExtraTime then
2158 local n = 30
2159 if extraTime ~= nil then
2160 n = extraTime
2161 TurnTimeLeft = TurnTimeLeft - 30000 + extraTime
2163 elseif ammoType == amExtraDamage then
2164 extraDamageUsed = true
2165 elseif ammoType == amLowGravity then
2166 if gravity ~= nil or lowGravity ~= nil then
2167 local base = 100
2168 if gravity then
2169 base = gravity
2171 if lowGravity == nil then
2172 lowGravity = 50
2174 SetGravity(div(base * lowGravity, 100))
2179 function FakeAmmoinfoCaption(ammoType)
2180 local ammoCount = GetAmmoCount(CurrentHedgehog, ammoType)
2181 local str
2183 local infStr, finStr
2185 if ammoType == amHellishBomb then
2186 finStr = loc("Hellish hand-grenade (%d), %d sec")
2187 infStr = loc("Hellish hand-grenade, %d sec")
2188 elseif ammoType == amDynamite then
2189 finStr = loc("Dynamite (%d), %d sec")
2190 infStr = loc("Dynamite, %d sec")
2191 elseif ammoType == amDrill then
2192 finStr = loc("Drill rocket (%d), %d sec")
2193 infStr = loc("Drill rocket, %d sec")
2194 elseif ammoType == amBallgun then
2195 finStr = loc("Ballgun (%d), %d sec")
2196 infStr = loc("Ballgun, %d sec")
2199 if ammoCount == 100 then
2200 str = string.format(infStr, manualTimer)
2201 else
2202 str = string.format(finStr, GetAmmoCount(CurrentHedgehog, ammoType), manualTimer)
2204 AddCaption(str, GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmoinfo)
2207 function onTimer(timer)
2208 manualTimer = timer
2209 local curAmmo = GetCurAmmoType()
2210 if CurrentHedgehog ~= nil then
2211 if (curAmmo == amHellishBomb and hhgTimer == "manual")
2212 or (curAmmo == amDynamite and dynamiteTimer == "manual")
2213 or (curAmmo == amDrill and drillRocketTimer == "manual")
2214 or (curAmmo == amBallgun and ballTimer == "manual") then
2215 FakeAmmoinfoCaption(curAmmo)
2220 function onSetWeapon()
2221 setWeaponMessage = true
2223 onSlot = onSetWeapon
2225 function onAttack()
2226 -- Show informational messages about remaining shots (if finite) for certain weapons
2227 -- TODO: Rewrite this part when the engine creates similar messages on its own
2228 if birdyGear ~= nil then
2229 if birdyEggs == "inf" then
2230 -- We kinda “fake” infinity by making sure we never run out of eggs
2231 SetFlightTime(birdyGear, 9001)
2232 else
2233 -- Finite eggs: Show number of remaining eggs
2234 AddCaption(string.format(loc("Eggs left: %d"), math.max(0, GetFlightTime(birdyGear)-1)), 0xFFFFFFFF, capgrpAmmostate)
2237 if rcPlaneGear ~= nil then
2238 if rcPlaneBombs == "inf" then
2239 -- We kinda “fake” infinity by making sure we never run out of bombs
2240 SetHealth(rcPlaneGear, 9001)
2241 else
2242 -- Finite bombs: Show number of remaining bombs
2243 AddCaption(string.format(loc("Bombs left: %d"), math.max(0, GetHealth(rcPlaneGear)-1)), 0xFFFFFFFF, capgrpAmmostate)
2246 if kamikazeGear ~= nil then
2247 if kamikazeTrigger then
2248 SetHealth(kamikazeGear, 0)
2253 function onNewTurn()
2254 gameStarted = true
2255 releaseShotInNextTick = false
2256 manualTimer = nil
2257 extraDamageUsed = false
2258 if buildLimit ~= nil then
2259 stuffBuilt = 0
2260 if buildRange == "inf" then
2261 SetMaxBuildDistance(0)
2262 elseif buildRange ~= nil then
2263 SetMaxBuildDistance(buildRange)
2264 else
2265 SetMaxBuildDistance()
2269 -- Initialize initial switch
2270 if initialSwitch then
2271 switchStage = 0
2274 -- No jumping mode
2275 if noJumping then
2276 SetInputMask(band(GetInputMask(), bnot(gmLJump + gmHJump)))
2279 -- Disable knocking
2280 if noKnock then
2281 runOnHogs(disableKnocking)
2284 -- Set gravity
2285 if gravity ~= nil then
2286 SetGravity(gravity)
2289 -- Adjust wind
2290 if fairWind or maxWind ~= nil then
2291 local newWind
2292 if maxWind ~= nil then
2293 if maxWind == 0 then
2294 newWind = 0
2295 else
2296 newWind = GetRandom(maxWind*2+1) - maxWind
2298 else
2299 newWind = GetRandom(201)-100
2302 if fairWind then
2303 if lastRound == nil or lastRound ~= TotalRounds then
2304 SetWind(newWind)
2306 elseif maxWind ~= nil then
2307 SetWind(newWind)
2312 -- Sudden Death specials
2313 if TotalRounds == SuddenDeathTurns then
2314 if sdState == 1 then
2315 sdState = 2
2316 -- Emulate Sudden Death effect if it is disabled by the game scheme
2317 if HealthDecrease == 0 and WaterRise == 0 and (sdPoison or sdOneHP) then
2318 AddCaption(loc("Sudden Death!"))
2319 PlaySound(sndSuddenDeath)
2321 if sdPoison then
2322 runOnHogs(function(hog)
2323 if poisonDamage ~= nil then
2324 SetEffect(hog, hePoisoned, poisonDamage)
2325 else
2326 SetEffect(hog, hePoisoned, 5)
2328 end)
2330 if sdOneHP then
2331 runOnHogs(function(hog)
2332 SetHealth(hog, 1)
2333 end)
2335 else
2336 sdState = 1
2340 -- Reset “poison kills” modifier for this turn
2341 if poisonKills then
2342 poisonKillDone = false
2345 -- Force-return TimeBoxes when using turn-based return setting
2346 if tardisReturnTurns ~= nil then
2347 for gear, value in pairs(tardisGears) do
2348 if GetGearPos(gear) == 4 and type(value) == "number" then
2349 tardisGears[gear] = tardisGears[gear] - 1
2350 if tardisGears[gear] <= 0 then
2351 SetTimer(gear, 0)
2352 tardisGears[gear] = nil
2358 -- Count rounds
2359 lastRound = TotalRounds
2362 function onGirderPlacement(frameIdx, x, y)
2363 if(buildLimit ~= nil) then
2364 stuffBuilt = stuffBuilt + 1
2365 AddCaption(string.format(loc("Buildings left: %d"), buildLimit - stuffBuilt), 0xFFFFFFFF, capgrpAmmostate)
2366 if stuffBuilt >= buildLimit then
2367 SetMaxBuildDistance(1)
2370 if strategicTools then
2371 strategicToolsHandlePlacement()
2375 onRubberPlacement = onGirderPlacement
2377 --[[ Helper functions ]]
2378 function hedgepot()
2379 local default_percent_min = 33
2380 local default_percent_max = 300
2381 local mutators = {
2382 { type = "GameFlag", name = "gfLaserSight" },
2383 { type = "GameFlag", name = "gfInvulnerable" },
2384 { type = "GameFlag", name = "gfResetHealth" },
2385 { type = "GameFlag", name = "gfVampire" },
2386 { type = "GameFlag", name = "gfKarma" },
2387 { type = "GameFlag", name = "gfShareAmmo" },
2388 { type = "GameFlag", name = "gfResetWeps" },
2389 { type = "GameFlag", name = "gfPerHogAmmo" },
2390 { type = "GameFlag", name = "gfBottomBorder" },
2391 { type = "CoreNumber", name = "HealthCaseProb", absolute_min = 0, absolute_max = 100 },
2392 { type = "CoreNumber", name = "DamagePercent" },
2393 { type = "CoreNumber", name = "MinesNum" },
2394 { type = "CoreNumber", name = "MineDudPercent", absoulte_min = 0, absolute_max = 100 },
2395 { type = "CoreNumber", name = "Explosives" },
2396 { type = "GameFlag", name = "gfVampire" },
2397 { type = "GameFlag", name = "gfKarma" },
2398 { type = "GameFlag", name = "gfMoreWind" },
2399 { type = "truth", name = "airFlamesHurt" },
2400 { type = "truth", name = "poison" },
2401 { type = "truth", name = "noJump" },
2402 { type = "truth", name = "noKnock" },
2403 { type = "truth", name = "teamCure" },
2404 { type = "truth", name = "shareHealth" },
2405 { type = "truth", name = "davidAndGoliath" },
2406 { type = "truth", name = "initialSwitch" },
2407 { type = "truth", name = "strategicTools" },
2408 { type = "truth", name = "sdPoison" },
2409 { type = "truth", name = "sdOneHP" },
2410 { type = "truth", name = "poisonKills" },
2411 { type = "truth", name = "epidemic" },
2412 { type = "truth", name = "maxPower" },
2413 { type = "truth", name = "fairWind" },
2414 { type = "enum", name = "stickyFlames", enum = { "always", "never" } },
2415 { type = "num", name = "maxWind", absolute_min = 0, absolute_max = 100, default = 100 },
2416 { type = "num", name = "gravity", absolute_min = 10, absolute_max = 200, default = 100 },
2417 { type = "num", name = "poisonDamage", absolute_min = 1, absolute_max = 20, default = 5 },
2418 { type = "num", name = "barrelHealth", default = 60 },
2419 { type = "num", name = "hogFriction", absolute_min = -10, absolute_max = 10, default = 0 },
2420 { type = "num", name = "buildLimit", absolute_min = 1, absolute_max = 5, default = 2 },
2422 local util_weapons_modifiers =
2424 { type = "truth", name = "kamikazeTrigger" },
2425 { type = "truth", name = "pickHammerStraightDown", text=loc("Pick hammer (no skewing)") },
2426 { type = "enum", name = "MinesTime", enum = { -1000, 0, 1000, 2000, 3000, 4000, 5000 } },
2427 { type = "enum", name = "tardisReturnTurns", enum = { 0, 1, 3, 5, "inf", } },
2428 { type = "set", name = "saucerFuel", value = "inf" },
2429 { type = "set", name = "birdyEnergy", value = "inf" },
2430 { type = "set", name = "birdyEggs", value = "inf", text=loc("Birdy eggs (infinite)") },
2431 { type = "set", name = "rcPlaneBombs", value = "inf", text=loc("RC Plane bombs (infinite)") },
2432 { type = "set", name = "hhgTimer", value = "manual", text=loc("Hellish hand-grenade timer (manual)")},
2433 { type = "set", name = "ballTimer", value = "manual", text=loc("Ball timer (manual)")},
2434 { type = "set", name = "buildRange", value = "inf" },
2435 { type = "num", name = "buildRange", default = 256, text=loc("Construction/Rubber range (%d%%)"), numtype="perc" },
2436 { type = "num", name = "saucerFuel", default = 2000 },
2437 { type = "num", name = "birdyEnergy", default = 2000 },
2438 { type = "num", name = "rcPlaneTimer", default = 15000},
2439 { type = "num", name = "freezerFuel", default = 1000, text=loc("Freezer fuel (%d%%)"), numtype="perc"},
2440 { type = "num", name = "flamethrowerFuel", default = 500, text=loc("Flamethrower fuel (%d%%)"), numtype="perc"},
2441 { type = "num", name = "cakeTimer", default = 2048, text=loc("Cake walking time (%d%%)"), numtype="perc"},
2442 { type = "num", name = "kamikazeRange", default = 2048, text=loc("Kamikaze range (%d%%)"), numtype="perc"},
2443 { type = "num", name = "ballTimer", default = 5000, text=loc("Ball timer (%.1fs)"),numtype="time"},
2444 { type = "num", name = "pickHammerTimer", default = 4000, text=loc("Pickhammer time (%.1fs)"), numtype="time"},
2445 { type = "num", name = "blowTorchTimer", default = 7500, text=loc("Blowtorch time (%.1fs)"), numtype="time"},
2446 { type = "num", name = "birdyEggs", default = 2, text=loc("Birdy eggs (%d)"), numtype="abs"},
2447 { type = "num", name = "rcPlaneBombs", default = 3, text=loc("RC Plane bombs (%d)"), numtype="abs"},
2448 { type = "num", name = "ballGunBalls", default = 51, text=loc("Number of ball gun balls (%d)"), numtype="abs"},
2449 { type = "num", name = "pianoBounces", default = 5, text=loc("Piano terrain bounces (%d)"), numtype="abs"},
2450 { type = "num", name = "airAttackBombs", default = 6, text=loc("Air attack bombs (%d)"), numtype="abs"},
2451 { type = "num", name = "mineStrikeMines", default = 6, text=loc("Mine strike mines (%d)"), numtype="abs"},
2452 { type = "num", name = "drillStrikeDrills", default = 6, text=loc("Drill strike drills (%d)"), numtype="abs"},
2453 { type = "num", name = "deagleStrength", default = 50, text=loc("Desert Eagle digging strength (%d%%)"), numtype="perc"},
2454 { type = "num", name = "sniperStrength", default = 50, text=loc("Sniper rifle digging strength (%d%%)"), numtype="perc"},
2455 { type = "num", name = "hammerStrength", default = 125, text=loc("Hammer digging strength (%d%%)"), numtype="perc"},
2456 { type = "num", name = "extraTime", default = 30000, absolute_min = 15000, absolute_max = 45000 },
2457 { type = "num", name = "lowGravity", default = 50},
2458 { type = "num", name = "drillRocketTimer", default = 5000, text=loc("Drill rocket timer (%.1fs)"), numtype="time"},
2459 { type = "num", name = "hhgTimer", default = 5000, text=loc("Hellish hand-grenade timer (%.1fs)"), numtype="time"},
2461 local conflicts = {
2462 sdPoison = { "sdOneHP" },
2463 sdOneHP = { "sdPoison" },
2464 gfInvulnerable = { "gfVampire", "gfKarma", "teamCure", "poison", "sdPoison", "shareHealth", "gfResetHealth" },
2465 gfVampire = { "gfInvulnerable" },
2466 gfKarma = { "gfInvulnerable" },
2467 teamCure = { "gfInvulnerable" },
2468 poison = { "gfInvulnerable" },
2469 sdPoison = { "gfInvulnerable" },
2470 shareHealth = { "gfInvulnverable" },
2471 gfResetHealth = { "gfInulnverable" },
2472 gfArtillery = { "noJump" },
2473 noJump = { "gfArtillery " },
2476 local text_counter = 0
2477 local texts = {}
2479 local function roll(reel)
2480 local pickID = GetRandom(#reel)+1
2481 local pick = reel[pickID]
2482 if pick.type == "GameFlag" then
2483 if GetGameFlag(_G[pick.name]) == true then
2484 DisableGameFlags(_G[pick.name])
2485 else
2486 EnableGameFlags(_G[pick.name])
2488 elseif pick.type == "CoreNumber" then
2489 if pick.absolute_min ~= nil and pick.absolute_max ~= nil then
2490 _G[pick.name] = GetRandom(pick.absolute_max - pick.absolute_min + 1) + pick.absolute_min
2491 else
2492 _G[pick.name] = div((GetRandom(default_percent_max - default_percent_min) + default_percent_min) * _G[pick.name], 100)
2494 elseif pick.type == "enum" then
2495 local e = GetRandom(#pick.enum - 1)+1
2496 _G[pick.name] = pick.enum[e]
2497 elseif pick.type == "truth" then
2498 _G[pick.name] = true
2499 elseif pick.type == "num" then
2500 if pick.absolute_min and pick.absolute_max then
2501 _G[pick.name] = GetRandom(pick.absolute_max - pick.absolute_min + 1) + pick.absolute_min
2502 else
2503 _G[pick.name] = div((GetRandom(default_percent_max - default_percent_min) + default_percent_min) * pick.default, 100)
2505 elseif pick.type == "set" then
2506 _G[pick.name] = pick.value
2508 if pick.text ~= nil then
2509 text_counter = text_counter + 1
2510 if pick.type == "num" then
2511 local arg
2512 if pick.numtype == "abs" then
2513 arg = round(_G[pick.name])
2514 elseif pick.numtype == "time" then
2515 arg = _G[pick.name]/1000
2516 else
2517 arg = round(_G[pick.name]/pick.default*100)
2519 table.insert(texts, string.format(pick.text, arg))
2520 elseif pick.type == "set" then
2521 table.insert(texts, string.format(pick.text))
2524 table.remove(reel, pickID)
2527 -- “Roll” the “reels”
2528 local rolls_1 = GetRandom(5)+1
2529 local weapons_mod = GetRandom(100)
2530 local rolls_2 = 0
2532 for i=1,rolls_1 do
2533 roll(mutators)
2535 if weapons_mod < 20 then
2536 roll(util_weapons_modifiers)
2539 return text_counter, texts
2542 -- Functions for strategic tools
2543 function strategicToolsEndTurn(baseRetreatTime, girderHack)
2544 SetState(CurrentHedgehog, bor(GetState(CurrentHedgehog), gstAttacked))
2545 local retreatTime
2546 retreatTime = div(baseRetreatTime * GetAwayTime, 100)
2548 -- This avoids the “Hurry up!” taunt from being played
2549 if retreatTime == 5000 then retreatTime = 5001 end
2551 if girderHack then
2552 --[[ This is an ugly hack which is needed after placement of a girder.
2553 It is not neccessary for the other weapons, but it works for those
2554 anyways.
2555 If a girder or rubber is placed, and it was not the last one,
2556 the player *actually can* still continue to select new weapons.
2557 This hack forces Hedgewars to un-select the girder or rubber
2558 so the hog can’t continue to select a new weapon.
2560 FIXME: Get rid of this hack as soon as this weirdness is fixed in
2561 Hedgewars.
2563 SetWeapon(amSkip)
2565 --[[ This is another hack to hide the previous message caused by selecting
2566 skip. ]]
2567 AddCaption(loc("Retreat!"), 0xFFFFFFFF, capgrpAmmoinfo)
2570 TurnTimeLeft = retreatTime
2573 function strategicToolsHandlePlacement()
2574 girderPlaced = true
2575 strategicToolsEndTurn(5000, true)
2578 -- Handle modified poison damage
2579 function adjustPoisonDamage(hog)
2580 if GetEffect(hog, hePoisoned) > 0 then
2581 if GetEffect(hog, hePoisoned) ~= poisonDamage then
2582 SetEffect(hog, hePoisoned, poisonDamage)
2587 -- Kill poisoned hogs with low health
2588 function doPoisonKill(hog)
2589 local poison = GetEffect(hog, hePoisoned)
2590 local health = GetHealth(hog)
2591 if poison > 0 then
2592 if health - poison <= 0 then
2593 SetHealth(hog, 0)
2598 -- Hogs of Steel mode
2599 function disableKnocking(gear)
2600 SetState(gear, bor(GetState(gear), gstNotKickable))
2603 -- Drop donor box if it was the last hog or the king
2604 function checkDonors(hog)
2605 if band(GetState(hog), gstHHDeath) ~= 0 and band(GetState(hog), gstDrowning) == 0 then
2606 if GetGameFlag(gfKing) and hogNumbers[GetHogTeamName(hog)][1] == hog then
2607 donors[hog] = true
2608 else
2609 hogCounter = 0
2610 runOnHogsInTeam(countHog, GetHogTeamName(hog))
2611 if hogCounter == 1 then
2612 donors[hog] = true
2618 -- Misc.
2619 -- Used for tracking functions to count hogs in a team etc.
2620 function countHog(gear)
2621 hogCounter = hogCounter + 1
2624 function round(num)
2625 if num >= 0 then
2626 return math.floor(num + 0.5)
2627 else
2628 return math.ceil(num - 0.5)
2632 -- Truncate string input number (safe math.floor(tonumber(x)) alternative)
2633 function truncStrNum(strNum)
2634 if strNum == nil then return nil end
2635 local s = string.match(strNum, "(%d*)")
2636 if s ~= nil then
2637 return tonumber(s)
2638 else
2639 return nil
2643 -- Changes the ammo count of ammoType of the hog
2644 -- It adds addCount to the hog
2645 -- If addCount is 100, the hog gets infinite ammo
2646 -- For finite numbers, the hog can never end up with more than 99 of the same ammo
2647 function modAmmo(hog, ammoType, addCount)
2648 local currentCount = GetAmmoCount(hog, ammoType)
2649 if showMessage == nil then showMessage = true end
2650 if currentCount ~= 100 and addCount ~= 100 then
2651 local newCount
2652 if addCount == 100 then
2653 newCount = 100
2654 else
2655 newCount = math.max(0, math.min(99, currentCount + addCount))
2657 AddAmmo(CurrentHedgehog, ammoType, newCount)