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.
11 Game Hacks adds additional game modifiers and settings in the following categories:
16 - Weapon parameters, including per-weapon damage (for 0.9.23 or later)
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
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):
57 Examples: 534, 54, -13
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
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%
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.
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.
92 Key Short Type Default Description
93 -----------------------+-------+-------+-------+-------------------------------------------------------------------------
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)
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).
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.
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.
245 - Minor annoyance: The hedgehog will have skip turn selected in the next turn after a girder was placed
246 - Disables infinite attack mode.
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
256 - Girders will be placed with a small buffer zone around them to avoid girders going to close to things and
258 - Hedgehogs and objects will NOT spawn on the girders; girders are inserted after everything else has been
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)
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
272 - Low gravity utility will modify gravity based on the base gravity
273 - Disables built-in low gravity game modifier
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
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
292 - Overwrites the built-in “disable wind” game modifier
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
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.
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.
312 - Barrels will start frozen if health is above 60 (this is only a graphical effect).
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
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
325 - If this is set, the health crate amount set in the game scheme will be ignored
328 - If the division leaves a remainder, the result is rounded down
331 - The donor box will replace the grave
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
342 --> The flying saucer has 200% fuel.
345 --> The flying saucer has 200% fuel.
348 --> The flying saucer has infinite fuel.
350 stickyminetimer=0, nojump=true
351 --> Sticky mines explode instantly and jumping is disabled.
354 --> Mines have a random time which may be between 1 and 2 seconds long.
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.
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.
369 --> Same as the previous example, but it uses the short form.
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 = {}
391 local healthCaseGearsTodo = {} -- List of health crates for which the health still needs to be set
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
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
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,
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)
439 return math.min(max, math.max(min, n))
441 return math.min(max, n)
443 return math.max(min, n)
448 -- This happens if the “unreasonable values” protection is off
453 function parseRange(key, str, unitParser, ...)
454 if str == nil then return nil end
456 subMin, subMax = string.match(str, "([^-]+)-([^-]+)")
457 if subMin ~= nil and subMax ~= nil then
458 minval = unitParser(key, subMin, ...)
459 maxval = unitParser(key, subMax, ...)
461 minval = unitParser(key, str, ...)
464 if minval ~= nil and maxval ~= nil then
465 if minval > maxval then
470 return minval, maxval
474 function parsePercentOrNumber(key, str, reference, min, max)
475 if str == nil then return nil end
478 if str == "inf" or str == "i" then
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)
491 if type(n) == "number" then
492 return minmax(n, min, max)
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
508 local len = string.len(str)
511 if str == "inf" or str == "i" then
514 elseif string.sub(str, len-1, len) == "ms" then
515 local substr = string.sub(str, 1, len-2)
516 n = truncStrNum(substr)
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*)")
525 prePoint = tonumber(prePoint)
527 if prePoint == nil then
531 local postPointN = {}
534 local subN = string.sub(postPoint, i, i)
535 if tonumber(subN) ~= nil then
536 postPointN[i] = tonumber(subN)
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)
555 if type(n) ~= "number" then
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
571 return minmax(n, min, max)
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
589 elseif type(n) == "number" then
590 return minmax(n, min, max)
596 function parseBool(key, str, default)
597 if str == "true" or str == "t" then
599 elseif str == "false" or str == "f" then
606 function multiParse(keys, parseFunction, ...)
608 local result = parseFunction(keys[i], params[keys[i]], ...)
609 if result ~= nil then
610 return parseFunction(keys[i], params[keys[i]], ...)
616 function onParameters()
620 Remove a free letter (for the short form) from this list when you introduce a new parameter with a 1-letter short form.
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
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
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
646 airMineTimerMax = 1300
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
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
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"
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"
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)
760 if SetGearValues ~= nil then
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)
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
810 ropeColor = multiParse({"ropecolor", "xr"}, parseHex, 0x0, 0xFFFFFF)
813 buildRange = multiParse({"buildrange", "J"}, parsePercentOrNumber, 256, 1)
816 function onGameInit()
817 -- Build desciption text
820 -- Activate the hedgepot
821 local hpModWeaps, hpTexts
822 if hedgePot == true then
823 hpModWeaps, hpTexts = hedgepot()
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)))
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."))
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
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."))
909 elseif mineTimerMin ~= mineTimerMax then
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)
914 str = string.format(loc("Mine timer: Mines explode after %.1f-%.1f seconds."), mineTimerMin/1000, mineTimerMax/1000)
916 table.insert(goalArray, str)
918 elseif mineTimerMin ~= nil then
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)
923 str = string.format(loc("Mine timer: Mines explode after %.1f second(s)."), mineTimerMin/1000)
925 table.insert(goalArray, str)
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
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)
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
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)
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
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)
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
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)
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."))
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)))
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] .. "|"
1029 -- Misc. setup (no messages)
1030 if ready ~= nil then
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)
1042 if gravity ~= nil then
1049 function onGearAdd(gear)
1050 local gt = GetGearType(gear)
1053 if gt == gtHedgehog then
1056 hogGears[gear] = true
1059 local teamName = GetHogTeamName(gear)
1060 if hogNumbers[teamName] == nil then
1062 hogNumbers[teamName] = { [1] = gear }
1064 table.insert(hogNumbers[teamName], gear)
1066 if teams[teamName] == nil then
1069 teams[teamName] = teams[teamName] + 1
1072 if poison == true then
1074 if poisonDamage == nil then
1079 SetEffect(gear, hePoisoned, dmg)
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))
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))
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
1172 if airMineTimerMin ~= airMineTimerMax then
1173 t = airMineTimerMin + GetRandom(airMineTimerMax-airMineTimerMin+1)
1178 SetGearValues(gear, nil, nil, t) -- WDTimer
1180 if airMineSeekSpeed ~= nil or airMineSeekRange ~= nil then
1181 local r = airMineSeekRange
1182 if airMineSeekRange == "inf" then
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
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)
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)
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
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
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)
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
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
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
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
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
1322 if type(ballTimer) == "number" then
1324 elseif ballTimer == "manual" then
1325 if manualTimer == nil then
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
1360 if type(hhgTimer) == "number" then
1362 elseif hhgTimer == "manual" then
1363 if manualTimer == nil then
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
1387 if type(dynamiteTimer) == "number" then
1388 timer = dynamiteTimer
1389 elseif dynamiteTimer == "manual" then
1390 if manualTimer == nil then
1393 timer = manualTimer*1000
1396 SetTimer(gear, timer)
1397 if timer > 10000 then
1399 elseif timer > 5000 then
1400 SetTag(gear, 32 + div(10000-timer, 166))
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
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)
1437 if beeTimer1 ~= nil then
1438 SetTimer(gear, beeTimer1)
1440 if beeTimer1 ~= nil or beeTimer2 ~= nil then
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
1453 if type(drillRocketTimer) == "number" then
1454 timer = drillRocketTimer
1455 elseif drillRocketTimer == "manual" then
1456 if manualTimer == nil then
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)
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
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
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
1523 SetTimer(gear, hammerStrength)
1526 if gt == gtPickHammer then
1527 if pickHammerTimer == "inf" then
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
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
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)
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)
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)
1584 if ropeOpacity ~= nil then
1585 opacityMask = bor(0xFFFFFF00, ropeOpacity)
1587 opacityMask = 0xFFFFFFFF
1590 if ropeColor == "clan" then
1591 actualColor = band(GetClanColor(GetHogClan(CurrentHedgehog)), opacityMask)
1592 elseif ropeColor ~= nil then
1593 actualColor = band(ropeColor*0x100+0xFF, opacityMask)
1595 actualColor = band(0xD8D8D8FF, opacityMask)
1598 -- Set rope to “line” mode (needed for coloization to work)
1601 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, actualColor)
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)
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)
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
1660 elseif gt == gtRCPlane then
1662 elseif gt == gtJetpack then
1664 elseif gt == gtDynamite then
1665 dynaGears[gear] = nil
1666 elseif gt == gtRope then
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)
1682 local x, y = GetGearPosition(gear)
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
1693 local maxAmmoID = 58 -- Ammo count in 0.9.22
1694 for i=1,maxAmmoID do
1697 for i=1,#ammoTypes do
1698 if ammoTypes[i] ~= amSkip then
1699 local count = GetAmmoCount(gear, ammoTypes[i])
1701 ammos[ammoTypes[i]] = count
1706 local box = SpawnFakeAmmoCrate(x, y, false, false)
1707 donorBoxes[box] = ammos
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
1733 runOnHogsInTeam(function(hog)
1734 SetEffect(hog, hePoisoned, 0)
1735 end, GetHogTeamName(CurrentHedgehog))
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.
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
1784 local hog = newTeamHogTable[i]
1785 if i > remainder then
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)
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
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)
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))
1858 elseif GetGearPos(bee) == 1 then
1859 SetTimer(bee, beeTimer2)
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
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)
1900 if poisonKills and not poisonKillDone then
1901 if TurnTimeLeft == 0 and CurrentHedgehog ~= nil then
1902 runOnHogs(doPoisonKill)
1903 poisonKillDone = true
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
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)
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
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
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
1959 elseif switchStage == 3 then
1960 SetGearMessage(CurrentHedgehog, gmAttack)
1961 elseif switchStage == 4 then
1963 AddAmmo(CurrentHedgehog, amSwitch, tmpSwitchCount)
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()
1986 if CurrentHedgehog ~= nil then
1987 if band(GetGearMessage(CurrentHedgehog), gmAttack) ~= 0 then
1988 local at = GetCurAmmoType()
1990 -- Check if this weapon is power-based
1991 for i=1,#powerWeapons do
1992 if at == powerWeapons[i] 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
2004 releaseShotInNextTick = true
2011 if strategicTools then
2012 if girderPlaced == true then
2013 girderPlaced = false
2014 strategicToolsEndTurn(4999, true)
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
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
2042 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, resurrectorRange, 4, 0xFFFF80EE)
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
2049 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, seductionRange, 4, 0xFF8080EE)
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)
2061 if rangeCircle ~= nil then
2062 DeleteVisualGear(rangeCircle)
2068 function onGameStart()
2070 -- Place random girders
2071 if girders ~= nil then
2073 -- Will be set to true if we have exceeded the tryLimit once
2074 local dontBotherTrying = false
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 ]]
2081 -- Try a couple of times to place a girder without being too close to other placed girders.
2083 while tries < tryLimit do
2084 -- Get a random girder position
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 ]]
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
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
2108 if placementOkay then
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
2121 table.insert(points, {x=x, y=y})
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
2135 local healthForGoliath = 0
2136 runOnHogsInTeam(function(hog)
2138 if hogCounter == 0 then
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
2150 -- Add all health to Goliath
2151 SetHealth(goliath, GetHealth(goliath) + healthForGoliath)
2156 function onHogAttack(ammoType)
2157 if ammoType == amExtraTime then
2159 if extraTime ~= nil then
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
2171 if lowGravity == nil then
2174 SetGravity(div(base * lowGravity, 100))
2179 function FakeAmmoinfoCaption(ammoType)
2180 local ammoCount = GetAmmoCount(CurrentHedgehog, ammoType)
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)
2202 str = string.format(finStr, GetAmmoCount(CurrentHedgehog, ammoType), manualTimer)
2204 AddCaption(str, GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmoinfo)
2207 function onTimer(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
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)
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)
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()
2255 releaseShotInNextTick = false
2257 extraDamageUsed = false
2258 if buildLimit ~= nil then
2260 if buildRange == "inf" then
2261 SetMaxBuildDistance(0)
2262 elseif buildRange ~= nil then
2263 SetMaxBuildDistance(buildRange)
2265 SetMaxBuildDistance()
2269 -- Initialize initial switch
2270 if initialSwitch then
2276 SetInputMask(band(GetInputMask(), bnot(gmLJump + gmHJump)))
2281 runOnHogs(disableKnocking)
2285 if gravity ~= nil then
2290 if fairWind or maxWind ~= nil then
2292 if maxWind ~= nil then
2293 if maxWind == 0 then
2296 newWind = GetRandom(maxWind*2+1) - maxWind
2299 newWind = GetRandom(201)-100
2303 if lastRound == nil or lastRound ~= TotalRounds then
2306 elseif maxWind ~= nil then
2312 -- Sudden Death specials
2313 if TotalRounds == SuddenDeathTurns then
2314 if sdState == 1 then
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)
2322 runOnHogs(function(hog)
2323 if poisonDamage ~= nil then
2324 SetEffect(hog, hePoisoned, poisonDamage)
2326 SetEffect(hog, hePoisoned, 5)
2331 runOnHogs(function(hog)
2340 -- Reset “poison kills” modifier for this turn
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
2352 tardisGears[gear] = nil
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 ]]
2379 local default_percent_min = 33
2380 local default_percent_max = 300
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"},
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
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])
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
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
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
2512 if pick.numtype == "abs" then
2513 arg = round(_G[pick.name])
2514 elseif pick.numtype == "time" then
2515 arg = _G[pick.name]/1000
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)
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))
2546 retreatTime = div(baseRetreatTime * GetAwayTime, 100)
2548 -- This avoids the “Hurry up!” taunt from being played
2549 if retreatTime == 5000 then retreatTime = 5001 end
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
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
2565 --[[ This is another hack to hide the previous message caused by selecting
2567 AddCaption(loc("Retreat!"), 0xFFFFFFFF, capgrpAmmoinfo)
2570 TurnTimeLeft = retreatTime
2573 function strategicToolsHandlePlacement()
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)
2592 if health - poison <= 0 then
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
2610 runOnHogsInTeam(countHog, GetHogTeamName(hog))
2611 if hogCounter == 1 then
2619 -- Used for tracking functions to count hogs in a team etc.
2620 function countHog(gear)
2621 hogCounter = hogCounter + 1
2626 return math.floor(num + 0.5)
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*)")
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
2652 if addCount == 100 then
2655 newCount = math.max(0, math.min(99, currentCount + addCount))
2657 AddAmmo(CurrentHedgehog, ammoType, newCount)