share/hedgewars/Data/Scripts/TargetPractice.lua
branchhedgeroid
changeset 15515 7030706266df
parent 14933 4c5fb1ee75b7
equal deleted inserted replaced
7861:bc7b6aa5d67a 15515:7030706266df
       
     1 --[=[
       
     2 Target Practice Mission Framework for Hedgewars
       
     3 
       
     4 This is a simple library intended to make setting up simple training missions a trivial
       
     5 task requiring just. The library has been created to reduce redundancy in Lua scripts.
       
     6 
       
     7 The training framework generates complete and fully usable training missions by just
       
     8 one function call.
       
     9 
       
    10 The missions generated by this script are all the same:
       
    11 - The player will get a team with a single hedgehog.
       
    12 - The team gets a single predefined weapon infinitely times.
       
    13 - A fixed sequence of targets will spawn at predefined positions.
       
    14 - When a target has been destroyed, the next target of the target sequence appears
       
    15 - The mission ends successfully when all targets have been destroyed
       
    16 - The mission ends unsuccessfully when the time runs out or the hedgehog dies
       
    17 - When the mission ends, a score is awarded, based on the performance (hit targets,
       
    18   accuracy and remaining time) of the hedgehog. When not all targets are hit, there
       
    19   will be no accuracy and time bonuses.
       
    20 
       
    21 To use this library, you first have to load it and to call TrainingMission once with
       
    22 the appropriate parameters. Really, that’s all!
       
    23 See the comment of TrainingMission for a specification of all parameters.
       
    24 
       
    25 Below is a template for your convenience, you just have to fill in the fields and delete
       
    26 optional arguments you don’t want.
       
    27 ----- snip -----
       
    28 HedgewarsScriptLoad("/Scripts/Training.lua")
       
    29 params = {
       
    30 	missionTitle = ,
       
    31 	map = ,
       
    32 	theme = ,
       
    33 	time = ,
       
    34 	ammoType = ,
       
    35 	gearType = ,
       
    36 	secondaryGearType = ,
       
    37 	targets = {
       
    38 		{ x = , y = },
       
    39 		{ x = , y = },
       
    40 		-- etc.
       
    41 	},
       
    42 
       
    43 	wind = ,
       
    44 	solidLand = ,
       
    45 	artillery = ,
       
    46 	clanColor = ,
       
    47 	goalText = ,
       
    48 	shootText =
       
    49 }
       
    50 TargetPracticeMission(params)
       
    51 ----- snip -----
       
    52 ]=]
       
    53 
       
    54 HedgewarsScriptLoad("/Scripts/Utils.lua")
       
    55 HedgewarsScriptLoad("/Scripts/Locale.lua")
       
    56 
       
    57 local player = nil
       
    58 local scored = 0
       
    59 local shots = 0
       
    60 local end_timer = 1000
       
    61 local game_lost = false
       
    62 local time_goal = 0
       
    63 local total_targets
       
    64 local targets
       
    65 local target_radar = false
       
    66 local next_target_circle = nil
       
    67 local gearsInGameCount = 0
       
    68 local gearsInGame = {}
       
    69 
       
    70 --[[
       
    71 TrainingMission(params)
       
    72 
       
    73 This function sets up the *entire* training mission and needs one argument: params.
       
    74 The argument “params” is a table containing fields which describe the training mission.
       
    75 	mandatory fields:
       
    76 	- missionTitle:	the name of the mission
       
    77 	- map:		the name map to be used
       
    78 	- theme:	the name of the theme (does not need to be a standalone theme)
       
    79 	- time:		the time limit in milliseconds
       
    80 	- ammoType:	the ammo type of the weapon to be used
       
    81 	- gearType:	the gear type of the gear which is fired (used to count shots and re-center camera)
       
    82 	- targets:	The coordinates of where the targets will be spawned.
       
    83 			It is a table containing tables containing coordinates of format
       
    84 			{ x=value, y=value }. The targets will be spawned in the same
       
    85 			order as specified the coordinate tables appear. Example:
       
    86 				targets = {
       
    87 					{ x = 324, y = 43 },
       
    88 					{ x = 123, y = 56 },
       
    89 					{ x = 6, y = 0 },
       
    90 				}
       
    91 			There must be at least 1 target.
       
    92 
       
    93 	optional fields:
       
    94 	- wind:		the initial wind (-100 to 100) (default: 0 (no wind))
       
    95 	- solidLand:	weather the terrain is indestructible (default: false)
       
    96 	- artillery:	if true, the hog can’t move (default: false)
       
    97 	- secGearType:	cluster of projectile gear (if present) (used to re-center camera)
       
    98 	- clanColor:	color of the (only) clan (default: -1, default first clan color)
       
    99 	- faceLeft:	if true, hog starts facing left, otherwise right (default: false)
       
   100 	- goalText:	A short string explaining the goal of the mission
       
   101 			(default: "Destroy all targets within the time!")
       
   102 	- shootText:	A string which says how many times the player shot, “%d” is replaced
       
   103 			by the number of shots. (default: "You have shot %d times.")
       
   104 	- useRadar	Whether to use target radar (small circles that mark the position
       
   105 			of the next target). (default: true). Note: Still needs to be unlocked.
       
   106 	- radarTint:	RGBA color of the target radar  (default: 0xFF3030FF). Use this field
       
   107 			if the target radar would be hard to see against the background.
       
   108 ]]
       
   109 
       
   110 
       
   111 local getTargetsScore = function()
       
   112 	return scored * math.ceil(6000/#targets)
       
   113 end
       
   114 
       
   115 function TargetPracticeMission(params)
       
   116 	if params.goalText == nil then params.goalText = loc("Eliminate all targets before your time runs out.|You have unlimited ammo for this mission.") end
       
   117 	if params.shootText == nil then params.shootText = loc("You have shot %d times.") end
       
   118 	if params.clanColor == nil then params.clanColor = -1 end
       
   119 	if params.faceLeft == nil then params.faceLeft = false end
       
   120 	if params.wind == nil then params.wind = 0 end
       
   121 	if params.radarTint == nil then params.radarTint = 0xFF3030FF end
       
   122 	if params.useRadar == nil then params.useRadar = true end
       
   123 
       
   124 	local solid, artillery
       
   125 	if params.solidLand == true then solid = gfSolidLand else solid = 0 end
       
   126 	if params.artillery == true then artillery = gfArtillery else artillery = 0 end
       
   127 
       
   128 	targets = params.targets
       
   129 
       
   130 	total_targets = #targets
       
   131 
       
   132 	_G.onAmmoStoreInit = function()
       
   133 		SetAmmo(params.ammoType, 9, 0, 0, 0)
       
   134 	end
       
   135 
       
   136 	_G.onGameInit = function()
       
   137 		Seed = 1
       
   138 		ClearGameFlags()
       
   139 		local attackMode
       
   140 		if (params.ammoType == amBee) then
       
   141 			attackMode = gfInfAttack
       
   142 		else
       
   143 			attackMode = gfMultiWeapon
       
   144 		end
       
   145 		EnableGameFlags(gfDisableWind, attackMode, gfOneClanMode, solid, artillery)
       
   146 		TurnTime = params.time
       
   147 		Map = params.map
       
   148 		Theme = params.theme
       
   149 		Goals = params.goalText
       
   150 		CaseFreq = 0
       
   151 		MinesNum = 0
       
   152 		Explosives = 0
       
   153 		-- Disable Sudden Death
       
   154 		WaterRise = 0
       
   155 		HealthDecrease = 0
       
   156 
       
   157 		SetWind(params.wind)
       
   158 
       
   159 		AddMissionTeam(params.clanColor)
       
   160 
       
   161 		player = AddMissionHog(1)
       
   162 		SetGearPosition(player, params.hog_x, params.hog_y)
       
   163 		HogTurnLeft(player, params.faceLeft)
       
   164 
       
   165 		local won = GetMissionVar("Won")
       
   166 		-- Unlock the target radar when the player has completed
       
   167 		-- the target practice before (any score).
       
   168 		-- Target radar might be disabled by config, however.
       
   169 		if won == "true" and params.useRadar == true then
       
   170 			target_radar = true
       
   171 		end
       
   172 
       
   173 	end
       
   174 
       
   175 	_G.onGameStart = function()
       
   176 		SendHealthStatsOff()
       
   177 		local recordInfo = getReadableChallengeRecord("Highscore")
       
   178 		ShowMission(params.missionTitle, loc("Aiming practice"), params.goalText .. "|" .. recordInfo, -params.ammoType, 5000)
       
   179 		SetTeamLabel(GetHogTeamName(player), "0")
       
   180 		spawnTarget()
       
   181 	end
       
   182 
       
   183 	_G.onNewTurn = function()
       
   184 		SetWeapon(params.ammoType)
       
   185 	end
       
   186 
       
   187 	_G.spawnTarget = function()
       
   188 		-- Spawn next target
       
   189 		local gear = AddGear(0, 0, gtTarget, 0, 0, 0, 0)
       
   190 
       
   191 		local x = targets[scored+1].x
       
   192 		local y = targets[scored+1].y
       
   193 
       
   194 		SetGearPosition(gear, x, y)
       
   195 
       
   196 		-- Target radar: Highlight position of the upcoming target.
       
   197 		-- This must be unlocked by the player first.
       
   198 		if target_radar then
       
   199 			if (not next_target_circle) and targets[scored+2] then
       
   200 				next_target_circle = AddVisualGear(0,0,vgtCircle,90,true)
       
   201 			end
       
   202 			if targets[scored+2] then
       
   203 				SetVisualGearValues(next_target_circle, targets[scored+2].x, targets[scored+2].y, 205, 255, 1, 20, nil, nil, 3, params.radarTint)
       
   204 			elseif next_target_circle then
       
   205 				DeleteVisualGear(next_target_circle)
       
   206 				next_target_circle = nil
       
   207 			end
       
   208 		end
       
   209 
       
   210 		return gear
       
   211 	end
       
   212 
       
   213 	_G.onGameTick20 = function()
       
   214 		if TurnTimeLeft < 40 and TurnTimeLeft > 0 and scored < total_targets and game_lost == false then
       
   215 			game_lost = true
       
   216 			AddCaption(loc("Time’s up!"), capcolDefault, capgrpGameState)
       
   217 			SetHealth(player, 0)
       
   218 			time_goal = 1
       
   219 		end
       
   220 
       
   221 		if band(GetState(player), gstDrowning) == gstDrowning and game_lost == false and scored < total_targets then
       
   222 			game_lost = true
       
   223 			time_goal = 1
       
   224 		end
       
   225 
       
   226 		if scored == total_targets  or game_lost then
       
   227 			if end_timer == 0 then
       
   228 				generateStats()
       
   229 				EndGame()
       
   230 				if scored == total_targets then
       
   231 					SetState(player, gstWinner)
       
   232 				end
       
   233 			end
       
   234 			end_timer = end_timer - 20
       
   235 		end
       
   236 
       
   237 		for gear, _ in pairs(gearsInGame) do
       
   238 			if band(GetState(gear), gstDrowning) ~= 0 then
       
   239 				-- Re-center camera on hog if projectile gears drown
       
   240 				gearsInGame[gear] = nil
       
   241 				gearsInGameCount = gearsInGameCount - 1
       
   242 				if gearsInGameCount == 0 and GetHealth(CurrentHedgehog) then
       
   243 					FollowGear(CurrentHedgehog)
       
   244 				end
       
   245 			end
       
   246 		end
       
   247 	end
       
   248 
       
   249 	_G.onGearAdd = function(gear)
       
   250 		if GetGearType(gear) == params.gearType then
       
   251 			shots = shots + 1
       
   252 		end
       
   253 		if GetGearType(gear) == params.gearType or (params.secGearType and GetGearType(gear) == params.secGearType) then
       
   254 			gearsInGameCount = gearsInGameCount + 1
       
   255 			gearsInGame[gear] = true
       
   256 		end
       
   257 	end
       
   258 
       
   259 	_G.onGearDamage = function(gear, damage)
       
   260 		if GetGearType(gear) == gtTarget then
       
   261 			scored = scored + 1
       
   262 			SetTeamLabel(GetHogTeamName(player), tostring(getTargetsScore()))
       
   263 			if scored < total_targets then
       
   264 				AddCaption(string.format(loc("Targets left: %d"), (total_targets-scored)), capcolDefault, capgrpMessage)
       
   265 				spawnTarget()
       
   266 			else
       
   267 				if not game_lost then
       
   268 					SaveMissionVar("Won", "true")
       
   269 					AddCaption(loc("You have destroyed all targets!"), capcolDefault, capgrpGameState)
       
   270 					ShowMission(params.missionTitle, loc("Aiming practice"), loc("Congratulations! You have destroyed all targets within the time."), 0, 0)
       
   271 					if shots <= scored then
       
   272 						-- No misses!
       
   273 						PlaySound(sndFlawless, player)
       
   274 					else
       
   275 						PlaySound(sndVictory, player)
       
   276 					end
       
   277 					SetEffect(player, heInvulnerable, 1)
       
   278 					time_goal = TurnTimeLeft
       
   279 					-- Disable control
       
   280 					SetInputMask(0)
       
   281 					AddAmmo(player, params.ammoType, 0)
       
   282 					SetTurnTimePaused(true)
       
   283 				end
       
   284 			end
       
   285 		end
       
   286 
       
   287 		if GetGearType(gear) == gtHedgehog then
       
   288 			if not game_lost then
       
   289 				game_lost = true
       
   290 
       
   291 				SetHealth(player, 0)
       
   292 				time_goal = 1
       
   293 			end
       
   294 		end
       
   295 	end
       
   296 
       
   297 	_G.onGearDelete = function(gear)
       
   298 		if GetGearType(gear) == gtTarget and band(GetState(gear), gstDrowning) ~= 0 then
       
   299 			AddCaption(loc("You lost your target, try again!"), capcolDefault, capgrpGameState)
       
   300 			local newTarget = spawnTarget()
       
   301 			local x, y = GetGearPosition(newTarget)
       
   302 			local success = PlaceSprite(x, y + 24, sprAmGirder, 0, 0xFFFFFFFF, false, false, false)
       
   303 			if not success then
       
   304 				WriteLnToConsole("ERROR: Failed to spawn girder under respawned target!")
       
   305 			end
       
   306 		elseif gearsInGame[gear] then
       
   307 			gearsInGame[gear] = nil
       
   308 			gearsInGameCount = gearsInGameCount - 1
       
   309 			if gearsInGameCount == 0 and GetHealth(CurrentHedgehog) then
       
   310 				-- Re-center camera to hog after all projectile gears were destroyed
       
   311 				FollowGear(CurrentHedgehog)
       
   312 			end
       
   313 		end
       
   314 	end
       
   315 
       
   316 	_G.generateStats = function()
       
   317 		local accuracy, accuracy_int
       
   318 		if(shots > 0) then
       
   319 			accuracy = (scored/shots)*100
       
   320 			accuracy_int = div(scored*100, shots)
       
   321 		end
       
   322 		local end_score_targets = getTargetsScore()
       
   323 		local end_score_overall
       
   324 		if not game_lost then
       
   325 			local end_score_time = math.ceil(time_goal/(params.time/6000))
       
   326 			local end_score_accuracy = 0
       
   327 			if(shots > 0) then
       
   328 				end_score_accuracy = math.ceil(accuracy * 60)
       
   329 			end
       
   330 			end_score_overall = end_score_time + end_score_targets + end_score_accuracy
       
   331 			SetTeamLabel(GetHogTeamName(player), tostring(end_score_overall))
       
   332 
       
   333 			SendStat(siGameResult, loc("You have finished the target practice!"))
       
   334 
       
   335 			SendStat(siCustomAchievement, string.format(loc("You have destroyed %d of %d targets (+%d points)."), scored, total_targets, end_score_targets))
       
   336 			SendStat(siCustomAchievement, string.format(params.shootText, shots))
       
   337 			if(shots > 0) then
       
   338 				SendStat(siCustomAchievement, string.format(loc("Your accuracy was %.1f%% (+%d points)."), accuracy, end_score_accuracy))
       
   339 			end
       
   340 			SendStat(siCustomAchievement, string.format(loc("You had %.1fs remaining on the clock (+%d points)."), (time_goal/1000), end_score_time))
       
   341 			if (not target_radar) and (#targets > 1) and (params.useRadar == true) then
       
   342 				SendStat(siCustomAchievement, loc("You have unlocked the target radar!"))
       
   343 			end
       
   344 
       
   345 			if(shots > 0) then
       
   346 				updateChallengeRecord("AccuracyRecord", accuracy_int)
       
   347 			end
       
   348 		else
       
   349 			SendStat(siGameResult, loc("Challenge over!"))
       
   350 
       
   351 			SendStat(siCustomAchievement, string.format(loc("You have destroyed %d of %d targets (+%d points)."), scored, total_targets, end_score_targets))
       
   352 			SendStat(siCustomAchievement, string.format(params.shootText, shots))
       
   353 			if(shots > 0) then
       
   354 				SendStat(siCustomAchievement, string.format(loc("Your accuracy was %.1f%%."), accuracy))
       
   355 			end
       
   356 			end_score_overall = end_score_targets
       
   357 		end
       
   358 		SendStat(siPointType, "!POINTS")
       
   359 		SendStat(siPlayerKills, tostring(end_score_overall), GetHogTeamName(player))
       
   360 		-- Update highscore
       
   361 		updateChallengeRecord("Highscore", end_score_overall)
       
   362 	end
       
   363 end