1-- Hedgewars SniperRifle Training 2-- Scripting Example 3 4-- Lines such as this one are comments - they are ignored 5-- by the game, no matter what kind of text is in there. 6-- It's also possible to place a comment after some real 7-- instruction as you see below. In short, everything 8-- following "--" is ignored. 9 10--------------------------------------------------------------- 11-- At first we implement the localization library using loadfile. 12-- This allows us to localize strings without needing to think 13-- about translations. 14-- We can use the function loc(text) to localize a string. 15 16HedgewarsScriptLoad("/Scripts/Utils.lua") 17HedgewarsScriptLoad("/Scripts/Locale.lua") 18 19-- This variable will hold the number of destroyed targets. 20local score = 0 21-- This variable will hold the number of shots from the sniper rifle 22local shots = 0 23-- This variable represents the number of targets to destroy. 24local score_goal = 27 25-- This variable controls how many milliseconds/ticks we'd 26-- like to wait before we end the round once all targets 27-- have been destroyed. 28local end_timer = 1000 -- 1000 ms = 1 s 29-- This variable is set to true if the game is lost (i.e. 30-- time runs out). 31local game_lost = false 32-- This variable will point to the hog's gear 33local player = nil 34-- Current target gear 35local target = nil 36-- This variable will grab the time left at the end of the round 37local time_goal = 0 38 39-- Like score, but targets before a blow-up sequence count double. 40-- Used to calculate final target score 41local score_bonus = 0 42 43local cinematic = false 44 45-- Timer of dynamite (shorter than usual) 46local dynamiteTimer = 2000 47-- Number of dynamite gears currently in game 48local dynamiteCounter = 0 49-- Table of dynamite gears, indexed by gear ID 50local dynamiteGears = {} 51 52-- Position for delayed targets 53local delayedTargetTargetX, delayedTargetY 54 55-- Team name of the player's team 56local playerTeamName 57 58-- This is a custom function to make it easier to 59-- spawn more targets with just one line of code 60-- You may define as many custom functions as you 61-- like. 62 63-- Spawns a target at (x, y) 64function spawnTarget(x, y) 65 -- add a new target gear 66 target = AddGear(x, y, gtTarget, 0, 0, 0, 0) 67 -- have the camera move to the target so the player knows where it is 68 FollowGear(target) 69end 70 71-- Remembers position to spawn a target at (x, y) after a dynamite explosion 72function spawnTargetDelayed(x, y) 73 delayedTargetX = x 74 delayedTargetY = y 75 -- The previous target always counts double after destruction 76 score_bonus = score_bonus + 1 77end 78 79function getTargetScore() 80 return score_bonus * 200 81end 82 83-- Cut sequence to blow up land with dynamite 84function blowUp(x, y, follow) 85 if cinematic == false then 86 cinematic = true 87 SetCinematicMode(true) 88 end 89 -- Spawn dynamite with short timer 90 local dyna = AddGear(x, y, gtDynamite, 0, 0, 0, dynamiteTimer) 91 -- Fix dynamite animation due to non-default timer 92 SetTag(dyna, div(5000-dynamiteTimer, 166)) 93 if follow then 94 FollowGear(dyna) 95 end 96end 97 98function onNewTurn() 99 SetWeapon(amSniperRifle) 100end 101 102-- This function is called before the game loads its 103-- resources. 104-- It's one of the predefined function names that will 105-- be called by the game. They give you entry points 106-- where you're able to call your own code using either 107-- provided instructions or custom functions. 108function onGameInit() 109 -- At first we have to overwrite/set some global variables 110 -- that define the map, the game has to load, as well as 111 -- other things such as the game rules to use, etc. 112 -- Things we don't modify here will use their default values. 113 114 -- The base number for the random number generator 115 Seed = 0 116 -- Game settings and rules 117 ClearGameFlags() 118 EnableGameFlags(gfMultiWeapon, gfOneClanMode, gfArtillery) 119 -- The time the player has to move each round (in ms) 120 TurnTime = 150000 121 -- The frequency of crate drops 122 CaseFreq = 0 123 -- The number of mines being placed 124 MinesNum = 0 125 -- The number of explosives being placed 126 Explosives = 0 127 -- The map to be played 128 Map = "Ropes" 129 -- The theme to be used 130 Theme = "Golf" 131 -- Disable Sudden Death 132 WaterRise = 0 133 HealthDecrease = 0 134 135 -- Create the player team 136 playerTeamName = AddMissionTeam(-1) 137 -- And add a hog to it 138 player = AddMissionHog(1) 139 SetGearPosition(player, 602, 1488) 140end 141 142-- This function is called when the round starts 143-- it spawns the first target that has to be destroyed. 144-- In addition it shows the scenario goal(s). 145function onGameStart() 146 -- Disable graph in stats screen 147 SendHealthStatsOff() 148 -- Spawn the first target. 149 spawnTarget(860,1020) 150 151 local highscore = getReadableChallengeRecord("Highscore") 152 -- Show some nice mission goals. 153 -- Parameters are: caption, sub caption, description, 154 -- extra text, icon and time to show. 155 -- A negative icon parameter (-n) represents the n-th weapon icon 156 -- A positive icon paramter (n) represents the (n+1)-th mission icon 157 -- A timeframe of 0 is replaced with the default time to show. 158 ShowMission(loc("Sniper Training"), loc("Aiming Practice"), 159 loc("Eliminate all targets before your time runs out.|You have unlimited ammo for this mission.") 160 .. "|" .. highscore, -amSniperRifle, 0) 161 162 -- Displayed initial player score 163 SetTeamLabel(playerTeamName, "0") 164end 165 166-- This function is called every game tick. 167-- Note that there are 1000 ticks within one second. 168-- You shouldn't try to calculate too complicated 169-- code here as this might slow down your game. 170function onGameTick20() 171 if game_lost then 172 return 173 end 174 -- If time's up, set the game to be lost. 175 -- We actually check the time to be "1 ms" as it 176 -- will be at "0 ms" right at the start of the game. 177 if TurnTimeLeft < 40 and TurnTimeLeft > 0 and score < score_goal and game_lost == false then 178 game_lost = true 179 -- and generate the stats and go to the stats screen 180 generateStats() 181 EndGame() 182 -- Just to be sure set the goal time to 1 ms 183 time_goal = 1 184 end 185 -- If the goal is reached or we've lost ... 186 if score == score_goal or game_lost then 187 -- ... check to see if the time we'd like to 188 -- wait has passed and then ... 189 if end_timer == 0 then 190 -- ... end the game ... 191 generateStats() 192 EndGame() 193 if score == score_goal then 194 SetState(CurrentHedgehog, gstWinner) 195 end 196 end 197 end_timer = end_timer - 20 198 end 199end 200 201-- This function is called when the game is initialized 202-- to request the available ammo and probabilities 203function onAmmoStoreInit() 204 -- add an unlimited supply of shotgun ammo 205 SetAmmo(amSniperRifle, 9, 0, 0, 0) 206end 207 208--[[ Re-center camera to target after using sniper rifle. 209This makes it easier to find the target. If we don't 210do this, the camera would contantly bounce back to 211the hog which would be annoying. ]] 212function onAttack() 213 if target and GetCurAmmoType() == amSniperRifle then 214 FollowGear(target) 215 end 216end 217 218-- Insta-blow up dynamite with precise key 219function onPrecise() 220 for gear, _ in pairs(dynamiteGears) do 221 SetTimer(gear, 0) 222 end 223end 224 225-- This function is called when a new gear is added. 226-- We use it to count the number of shots, which we 227-- in turn use to calculate the final score and stats 228function onGearAdd(gear) 229 if GetGearType(gear) == gtSniperRifleShot then 230 shots = shots + 1 231 elseif GetGearType(gear) == gtDynamite then 232 dynamiteCounter = dynamiteCounter + 1 233 dynamiteGears[gear] = true 234 end 235end 236 237-- This function is called before a gear is destroyed. 238-- We use it to count the number of targets destroyed. 239function onGearDelete(gear) 240 local gt = GetGearType(gear) 241 242 if gt == gtCase then 243 game_lost = true 244 return 245 end 246 247 if (gt == gtDynamite) then 248 -- Dynamite blow-up, used to continue the game. 249 dynamiteCounter = dynamiteCounter - 1 250 dynamiteGears[gear] = nil 251 252 -- Wait for all dynamites to be destroyed before we continue. 253 -- Most cut scenes spawn multiple dynamites. 254 if dynamiteCounter == 0 then 255 if cinematic then 256 cinematic = false 257 SetCinematicMode(false) 258 end 259 -- Now *actually* spawn the delayed target 260 spawnTarget(delayedTargetX, delayedTargetY) 261 end 262 return 263 end 264 265 if gt == gtTarget then 266 target = nil 267 -- Add one point to our score/counter 268 score = score + 1 269 score_bonus = score_bonus + 1 270 -- If we haven't reached the goal ... 271 if score < score_goal then 272 -- ... spawn another target. 273 if score == 1 then 274 spawnTarget(1520,1350) 275 elseif score == 2 then 276 spawnTarget(1730,1040) 277 elseif score == 3 then 278 spawnTarget(2080,780) 279 elseif score == 4 then 280 -- Short cut scene, blows up up lots up land and prepares 281 -- next target position. 282 AddCaption(loc("Good so far!") .. " " .. loc("Keep it up!")); 283 blowUp(1730,1226) 284 blowUp(1440,1595) 285 blowUp(1527,1575) 286 blowUp(1614,1595) 287 blowUp(1420,1675, true) 288 blowUp(1527,1675) 289 blowUp(1634,1675) 290 blowUp(1440,1755) 291 blowUp(1527,1775) 292 blowUp(1614,1755) 293 -- Target appears *after* the cutscene. 294 spawnTargetDelayed(1527,1667) 295 elseif score == 5 then 296 spawnTarget(2175,1300) 297 elseif score == 6 then 298 spawnTarget(2250,940) 299 elseif score == 7 then 300 spawnTarget(2665,1540) 301 elseif score == 8 then 302 spawnTarget(3040,1160) 303 elseif score == 9 then 304 spawnTarget(2930,1500) 305 elseif score == 10 then 306 -- The "tricky" target. 307 -- It spawns behind a wall 308 -- and needs at least 2 shots. 309 AddCaption(loc("This one's tricky.")); 310 spawnTarget(700,720) 311 elseif score == 11 then 312 AddCaption(loc("Well done.")); 313 blowUp(914,1222) 314 blowUp(1050,1222) 315 blowUp(1160,1008) 316 blowUp(1160,1093) 317 blowUp(1160,1188) 318 blowUp(375,911) 319 blowUp(510,911) 320 blowUp(640,911) 321 blowUp(780,911) 322 blowUp(920,911) 323 blowUp(1060,913) 324 blowUp(1198,913, true) 325 spawnTargetDelayed(1200,830) 326 elseif score == 12 then 327 spawnTarget(1430,450) 328 elseif score == 13 then 329 spawnTarget(796,240) 330 elseif score == 14 then 331 spawnTarget(300,10) 332 elseif score == 15 then 333 spawnTarget(2080,820) 334 elseif score == 16 then 335 AddCaption(loc("Demolition is fun!")); 336 blowUp(2110,920) 337 blowUp(2210,920) 338 blowUp(2200,305) 339 blowUp(2300,305) 340 blowUp(2300,400, true) 341 blowUp(2300,500) 342 blowUp(2300,600) 343 blowUp(2300,700) 344 blowUp(2300,800) 345 blowUp(2300,900) 346 blowUp(2401,305) 347 blowUp(2532,305) 348 blowUp(2663,305) 349 spawnTargetDelayed(2300,760) 350 elseif score == 17 then 351 spawnTarget(2738,190) 352 elseif score == 18 then 353 spawnTarget(2590,-100) 354 elseif score == 19 then 355 AddCaption(loc("Will this ever end?")); 356 blowUp(2790,305) 357 blowUp(2930,305) 358 blowUp(3060,305) 359 blowUp(3190,305) 360 blowUp(3310,305, true) 361 blowUp(3393,613) 362 blowUp(2805,370) 363 blowUp(2805,500) 364 blowUp(2805,630) 365 blowUp(2805,760) 366 blowUp(2805,890) 367 blowUp(3258,370) 368 blowUp(3258,475) 369 blowUp(3264,575) 370 spawnTargetDelayed(3230,290) 371 elseif score == 20 then 372 spawnTarget(3670,250) 373 elseif score == 21 then 374 spawnTarget(2620,-100) 375 elseif score == 22 then 376 spawnTarget(2870,300) 377 elseif score == 23 then 378 spawnTarget(3850,900) 379 elseif score == 24 then 380 spawnTarget(3780,300) 381 elseif score == 25 then 382 spawnTarget(3670,0) 383 elseif score == 26 then 384 AddCaption(loc("Last Target!")); 385 spawnTarget(3480,1200) 386 end 387 elseif not game_lost then 388 -- Victory! 389 SaveMissionVar("Won", "true") 390 AddCaption(loc("Victory!"), capcolDefault, capgrpGameState) 391 ShowMission(loc("Sniper Training"), loc("Aiming Practice"), loc("Congratulations! You've eliminated all targets|within the allowed time frame."), 0, 0) 392 -- Play voice 393 if shots-1 <= score then 394 -- Flawless victory condition: Only 1 shot more than targets 395 -- (1 shot per "normal" target + 2 shots for the "tricky" target) 396 PlaySound(sndFlawless, CurrentHedgehog) 397 else 398 -- "Normal" victory 399 PlaySound(sndVictory, CurrentHedgehog) 400 end 401 402 FollowGear(CurrentHedgehog) 403 404 -- Unselect sniper rifle and disable hog controls 405 SetInputMask(0) 406 SetWeapon(amNothing) 407 AddAmmo(CurrentHedgehog, amSniperRifle, 0) 408 409 -- Save the time left so we may keep it. 410 time_goal = TurnTimeLeft 411 412 -- Freeze the clock because the challenge has been completed 413 SetTurnTimePaused(true) 414 end 415 SetTeamLabel(playerTeamName, getTargetScore()) 416 end 417end 418 419-- This function calculates the final score of the player and provides some texts and 420-- data for the final stats screen 421function generateStats() 422 local accuracy 423 local accuracy_int 424 if shots > 0 then 425 -- NOTE: 100% accuracy is not possible due to the "tricky" target. 426 accuracy = (score/shots)*100 427 accuracy_int = div(score*100, shots) 428 end 429 local end_score_targets = getTargetScore() 430 local end_score_overall 431 if not game_lost then 432 local end_score_time = math.ceil(time_goal/5) 433 local end_score_accuracy = 0 434 if shots > 0 then 435 end_score_accuracy = math.ceil(accuracy * 100) 436 end 437 end_score_overall = end_score_time + end_score_targets + end_score_accuracy 438 SetTeamLabel(playerTeamName, tostring(end_score_overall)) 439 440 SendStat(siGameResult, loc("You have successfully finished the sniper rifle training!")) 441 SendStat(siCustomAchievement, string.format(loc("You have destroyed %d of %d targets (+%d points)."), score, score_goal, end_score_targets)) 442 SendStat(siCustomAchievement, string.format(loc("You have made %d shots."), shots)) 443 if end_score_accuracy > 0 then 444 SendStat(siCustomAchievement, string.format(loc("Accuracy bonus: +%d points"), end_score_accuracy)) 445 end 446 SendStat(siCustomAchievement, string.format(loc("You had %.2fs remaining on the clock (+%d points)."), (time_goal/1000), end_score_time)) 447 448 if(shots > 0) then 449 updateChallengeRecord("AccuracyRecord", accuracy_int) 450 end 451 else 452 SendStat(siGameResult, loc("Challenge over!")) 453 454 SendStat(siCustomAchievement, string.format(loc("You have destroyed %d of %d targets (+%d points)."), score, score_goal, end_score_targets)) 455 SendStat(siCustomAchievement, string.format(loc("You have made %d shots."), shots)) 456 end_score_overall = end_score_targets 457 end 458 SendStat(siPointType, "!POINTS") 459 SendStat(siPlayerKills, tostring(end_score_overall), playerTeamName) 460 updateChallengeRecord("Highscore", end_score_overall) 461end 462 463