SingleEvent Scripts

From PMDOWiki

These events go into the event_single.lua file of your mod. As of version v0.7.25, you will need to copy this file from base PMDO into your mod first, then paste these scripts into it.

Custom Boss Fight Win Conditions / End effects:

Author: Palika

Want to have it so a boss fight has an alternative win condition than just "beat all enemies"? Want something special to happen when that win condition is met? If the answer is yes, you'll need to override the usual BeginBattleEvent and its corresponding CheckBossClearEvent and end_sequence responsible for these features with the Lua version.

Code

---
-- Reimplementation of Audino's C# event for BeginBattleEvent.
-- This will allow us to inject custom function code before ending a battle 
-- (such as clearing existing lava flows when a boss fight ends).
-- @tparam GameEventOwner owner
-- @tparam Character ownerChar
-- @tparam SingleCharContext context
-- @tparam table args Table of format {CustomClearEvent="NameOfFunctionThatRunsOnClear", [other arguments to said script]}
function SINGLE_CHAR_SCRIPT.LuaBeginBattleEvent(owner, ownerChar, context, args)
	local MapCheckState = luanet.import_type('RogueEssence.Dungeon.MapCheckState')
	local SingleCharScriptEvent = luanet.import_type('RogueEssence.Dungeon.SingleCharScriptEvent')
	
	local map_clear_idx = 'map_clear_check'

	if context.User ~= nil then return end
	--if a custom clear event is not given, use the default one.
	if args.CustomClearEvent == nil then args.CustomClearEvent = 'LuaCheckBossClearEvent' end
	
	--Turn on Team Mode if allowed when the boss fight starts.
	if _DUNGEON:CanUseTeamMode() then
		_DUNGEON:SetTeamMode(true)
	end
	
	local clear_status = RogueEssence.Dungeon.MapStatus(map_clear_idx)
	clear_status:LoadFromData()
	
	local check = clear_status.StatusStates:GetWithDefault(luanet.ctype(MapCheckState))
	--The 2nd argument in the function below needs a string that represents a lua table of the arguments to pass. Serpent.line will convert the lua table to a string representing it for us.
	--We only NEED to pass args, as owner, ownerchar, and context are automatically passed in when the check event is called
	check.CheckEvents:Add(SingleCharScriptEvent(args.CustomClearEvent, Serpent.line(args)))
	--check.CheckEvents:Add(LuaCheckBossClearEvent(owner, ownerChar, context, args))
	
	TASK:WaitTask(_DUNGEON:AddMapStatus(clear_status))
end

--Import Serpent library.
Serpent = require 'lib.serpent'

---
-- Reimplementation of the basic CheckBossClearEvent. 
-- Call something different from LuaBeginBattleEvent or edit this accordingly if you 
-- want a different wincon for your map or special effects/anims on win.
-- @tparam GameEventOwner owner
-- @tparam Character ownerChar Not used by this function.
-- @tparam SingleCharContext context Not used by this function.
-- @tparam table args Table of caller-defined arguments. Not used by this function.
function SINGLE_CHAR_SCRIPT.LuaCheckBossClearEvent(owner, ownerChar, context, args)
	local MapCheckState = luanet.import_type('RogueEssence.Dungeon.MapCheckState')
	local SingleCharScriptEvent = luanet.import_type('RogueEssence.Dungeon.SingleCharScriptEvent')
	
	---
	-- Sequence that runs when map is over. Fade out, cut the music, etc.
	-- @usage Coroutine
	function end_sequence()
		_GAME:BGM("", true)
		 
		TASK:WaitTask(_GAME:FadeOut(false))
		 
		_DUNGEON:ResetTurns()
		
		-- restore all and remove all map status
		local statuses_to_remove = {}
		for i = 0, _ZONE.CurrentMap.Status.Keys.Count - 1, 1 do
			statuses_to_remove[i] = _ZONE.CurrentMap.Status.Keys[i]
		end
		
		for i = 0, #statuses_to_remove - 1, 1 do
			TASK:WaitTask(_DUNGEON:RemoveMapStatus(statuses_to_remove[i], false))
		end 
		
		-- heal everyone in the party
		for i = 0, GAME:GetPlayerPartyCount() - 1, 1 do
			_DATA.Save.ActiveTeam.Players[i]:FullRestore()
		end
		
		TASK:WaitTask(_GAME:EndSegment(RogueEssence.Data.GameProgress.ResultType.Cleared))

	end

	-- For each enemy team, check each chara in that team. 
	-- If any are still alive, then fail this check and return early.
	for i = 0, _ZONE.CurrentMap.MapTeams.Count - 1, 1 do 
		local team = _ZONE.CurrentMap.MapTeams[i].Players
		for j = 0, team.Count - 1, 1 do
			-- Break and return early if even one enemy is not dead.
			if not team[j].Dead then return end
		end
	end
	
	-- Everyone's dead, clear the scene.
	local checks = owner.StatusStates:GetWithDefault(luanet.ctype(MapCheckState))
	
	-- The call originally for this was to remove(this), which isn't in lua. 
	-- So we need to find the LuaCheckBossClearEvent and remove that (remove ourself)
	for i = 0, checks.CheckEvents.Count - 1, 1 do
		if LUA_ENGINE:TypeOf(checks.CheckEvents[i]) == luanet.ctype(SingleCharScriptEvent) then
			if checks.CheckEvents[i].Script == args.CustomClearEvent then
				checks.CheckEvents:Remove(checks.CheckEvents[i])
			end
		end
	end
	
	if _DATA.CurrentReplay == nil then
		TASK:WaitTask(end_sequence())
	else 
		TASK:WaitTask(_GAME:EndSegment(RogueEssence.Data.GameProgress.ResultType.Cleared))
	end
	
end

Usage

The first function, LuaBeginBattleEvent, is responsible for letting PMDO know to look at LuaCheckBossClearEvent to determine when the map is won. Instead of the usual BeginBattleEvent in your boss fight's OnMapStart's Map Effects, you need to call a SingleCharScriptEvent for LuaBeginBattleEvent instead. It'll look like this:

LuaCheckBossClearEvent is responsible for identifying when the map is won, and then the end_sequence function within it is responsible for fading stuff out and actually winning the map.

You won't need to modify the scripting for LuaBeginBattleEvent at all; it should remain as it is.

Tutorial: Alternate win condition

If you would like an alternate win condition or end sequence besides just fading out, then you will need to edit LuaCheckBossClearEvent and its end_sequence respectively.

  1. First, copy and rename your custom version of LuaCheckBossClearEvent to something more appropriately fitting.
    • Let's say we want a win condition where you only have to defeat the leader of a gang to win the boss fight. We'll call this function instead CheckDefeatedGangLeader. Copy the LuaCheckBossClearEvent function above and rename it to CheckDefeatedGangLeader.
  2. In LuaBeginBattleEvent, be sure to pass an argument of CustomClearEvent = 'CheckDefeatedGangLeader' when you define it in the MapEffects, so LuaBeginBattleEvent will knows to use your custom function instead of the default one given. This is done in the Arg Table in the picture above. The value there should look something like {CustomClearEvent = 'CheckDefeatedGangLeader'}.
  3. Next, you need to modify how CheckDefeatedGangLeader identifies the win condition. If the win condition is failed, the function should return early. The default one does this by checking all enemy teams, and if any members on any team are not dead, then that means an enemy is still alive and the win condition has failed.
    • For our example, if our gang leader is a Scrafty, we should iterate through all enemy teams and see if we find a member that is both a Scrafty and alive. If so, then our win condition has failed and we should return early.
    • Alternatively, you can assign a LuaTable property to the Scrafty that marks it as, say, .GangLeader = true, and make sure that there are no NPCs with a LuaTable property of 'GangLeader' equal to true that are alive. Doing it like this would allow you to have as many GangLeaders as you may want that need to be defeated to meet the win condition.
  4. With all this done, when the Scrafty is defeated, the map should end and call end_sequence() as long as we aren't watching a replay. end_sequence() is responsible for fading the map and music out.
    • You can redefine it to anything you want if you want different things to happen when you actually win. For example, maybe you want your party members to face the camera and do a pose for a few seconds before the game fades out. You could accomplish this by writing code at the very top of end_sequence that makes all your party members do the face down and do the Pose animation!
    • You can do anything of course, but be careful not to remove things that may be important to handling the end of the dungeon adventure, such as the line of code responsible for ending the segment as Cleared.

Fail Dungeon When Ally/Partner Faints

Author: Palika

Want to cause a party wipe in the event that a specific character faints? Perhaps the Hero/Partner characters? This is easily achievable again via pre-made Lua scripting to be placed within event_single.lua.

-- We'll start by setting up the functionality. Place the following code within event_single.lua: -->

Code

---
-- Ends a run in a failure if an allied party member marked with "IsPartner" faints.
-- Additionally, if someone faints and they aren't "IsPartner", then send them home automatically.
-- @tparam nil owner This function accepts four arguments, but none of them are checked.
-- @tparam nil ownerChar
-- @tparam nil context
-- @tparam nil args
function SINGLE_CHAR_SCRIPT.AllyDeathCheck(owner, ownerChar, context, args)
	local player_count = GAME:GetPlayerPartyCount()
	local guest_count = GAME:GetPlayerGuestCount()

	if player_count < 1 then return end -- If there's no party members then we dont need to do anything

	for i = 0, player_count - 1, 1 do
		local player = GAME:GetPlayerPartyMember(i)
		if player.Dead and player.IsPartner then --someone died
			for j = 0, player_count - 1, 1 do --beam everyone else out
				player = GAME:GetPlayerPartyMember(j)
				if not player.Dead then    --dont beam out whoever died
					--delay between beam outs
					GAME:WaitFrames(60)
					TASK:WaitTask(_DUNGEON:ProcessBattleFX(player, player, _DATA.SendHomeFX))
					player.Dead = true
				end
			end
			--beam out guests
			for j = 0, guest_count - 1, 1 do --beam everyone else out
				guest = GAME:GetPlayerGuestMember(j)
				if not guest.Dead then --dont beam out whoever died
					--delay between beam outs
					GAME:WaitFrames(60)
					TASK:WaitTask(_DUNGEON:ProcessBattleFX(guest, guest, _DATA.SendHomeFX))
					guest.Dead = true
				end
			end
			return --cut the script short here if someone died, no need to check guests
		end
	end

	-- check guests as well
	if guest_count < 1 then return end -- If there's no guest members then we dont need to do anything
	for i = 0, guest_count - 1, 1 do
		local guest = GAME:GetPlayerGuestMember(i)
		if guest.Dead and guest.IsPartner then -- someone died
			-- beam player's team out first
			for j = 0, player_count - 1, 1 do -- beam everyone else out
				player = GAME:GetPlayerPartyMember(j)
				if not player.Dead then  -- dont beam out whoever died
					TASK:WaitTask(_DUNGEON:ProcessBattleFX(player, player, _DATA.SendHomeFX))
					player.Dead = true
					GAME:WaitFrames(60)
				end
			end
			for j = 0, guest_count - 1, 1 do --beam everyone else out
				guest = GAME:GetPlayerGuestMember(j)
				if not guest.Dead then -- dont beam out whoever died
					TASK:WaitTask(_DUNGEON:ProcessBattleFX(guest, guest, _DATA.SendHomeFX))
					guest.Dead = true
					GAME:WaitFrames(60)
				end
			end
		end
	end
end

Usage

Once this code has been added to event_single.lua, we need to set up Universal to call this code On Deaths.

  1. In the Dev Mode menu, go to the Constants tab, click Universal, and scroll down to On Deaths.
  2. Expand the section there, and click Add. We'll add a SingleCharScriptEvent for AllyDeathCheck here.
  3. Once you do that, make sure you set the priority for this event to 10. This will make it so the AllyDeathCheck code we put in event_single.lua earlier will run whenever someone dies at the right timing relative to other scripts.

Your config should look something like this after:

You're all set now! There's only one more thing you need to do. The script only actually checks to see if characters marked as "IsPartner" are fainted. IsPartner does not literally mean they are a partner; it is an attribute of Pokémon on your team you can set via scripting that determine whether they can leave the party or not. Thus, it's important to give, for example, the Hero character this attribute as well.

The script provided above also uses that flag to determine if the character should cause a party wipe upon their death. So be sure in your scripts to mark any important party members IsPartner flag to true if you want them to cause a wipe.
The following line of code in any script would set the character in the first slot of your party to not be removable from your team, and cause a wipe with the code from earlier if they faint:

GAME:GetPlayerPartyMember(0).IsPartner = true 

IsPartner Pokémon are usually the kind of teammate you'd want to cause a wipe if they faint, but you can, of course, change the code above to check for other attributes or conditions instead.

Low HP Exclamation Point

Author: Palika

Want that cool little effect from the original games where characters low on HP have an exclamation point over their head? Using some pre-configured event_single Lua code and by configuring your own Critical Health status, you can achieve this cosmetic effect pretty easily.

Code

---
-- Gives any party member with 1/4 their max HP the critical_health status, 
-- and removes it from any party member with more that 1/4 their max HP.
-- @tparam nil owner This function accepts four arguments, but none of them are checked.
-- @tparam nil ownerChar
-- @tparam nil context
-- @tparam nil args
function SINGLE_CHAR_SCRIPT.SetCriticalHealthStatus(owner, ownerChar, context, args)
	local player_count = GAME:GetPlayerPartyCount()
	local critical = RogueEssence.Dungeon.StatusEffect("critical_health")

	-- initialize status data before adding it to anything
	critical:LoadFromData()
	for i = 0, player_count - 1, 1 do
	local player = GAME:GetPlayerPartyMember(i)
		if player.HP <= player.MaxHP / 4 and player:GetStatusEffect("critical_health") == nil then 
			TASK:WaitTask(player:AddStatusEffect(nil, critical, false))
		elseif player.HP > player.MaxHP / 4 and player:GetStatusEffect("critical_health") ~= nil then
			TASK:WaitTask(player:RemoveStatusEffect("critical_health"))
		end
	end
end

Usage

First, configure the status effect that'll give the player the exclamation point over their head.

  1. Go to the Data tab on the Dev menu, then go to Statuses.
  2. Click the Add button on the menu that pops up, and it's here we'll configure our Critical Health status.
  3. Configure yours to match what you see in the image below. Anything you don't see in the window in this image can be left as the defaults.

Next, paste the above code into the event_single.lua file for your mod.

Once you've done that, we need to configure Universal to run this code at the end of turns:

  1. Click the Constants tab on the Dev menu, go to Universal, and scroll down until you find "On Turn Ends".
  2. Here, add a SingleCharScriptEvent that calls SetCriticalHealthStatus at the end turn. This will set or remove the Critical Health status that displays the exclamation point when someone's HP passes the 1/4 HP threshold.

Your configuration should look like this in the end:

You should be all set! Your ally team members (but not guests) will show an exclamation above their head and an informative status when they're low on HP.