Skip to content

Commit

Permalink
Refactor earthquakes to its own class
Browse files Browse the repository at this point in the history
  • Loading branch information
tobylane committed May 22, 2024
1 parent efc21ac commit 7d8fe7f
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 252 deletions.
6 changes: 3 additions & 3 deletions .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ globals = { -- Globals
-- Game classes
"AIHospital", "AnimationManager", "AnimationEffect", "App", "Audio",
"CallsDispatcher", "Cheats", "ChildClass", "Command", "Door", "DrawFlags",
"DummyRootNode", "EndConditions", "Entity", "EntityMap", "Epidemic",
"FileSystem", "FileTreeNode", "FilteredFileTreeNode", "GameUI", "Graphics",
"GrimReaper", "Hospital", "Humanoid", "HumanoidRawWalk",
"DummyRootNode", "Earthquake","EndConditions", "Entity", "EntityMap",
"Epidemic", "FileSystem", "FileTreeNode", "FilteredFileTreeNode", "GameUI",
"Graphics", "GrimReaper", "Hospital", "Humanoid", "HumanoidRawWalk",
"Inspector", "LoadGame", "LoadGameFile", "Litter", "Machine",
"Map", "MoviePlayer", "NoRealClass", "Object", "ParentClass",
"Patient", "Plant", "PlayerHospital", "Queue", "ResearchDepartment", "Room",
Expand Down
2 changes: 1 addition & 1 deletion CorsixTH/Lua/app.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ local runDebugger = corsixth.require("run_debugger")
-- and add compatibility code in afterLoad functions
-- Recommended: Also replace/Update the summary comment

local SAVEGAME_VERSION = 188 -- Add groups to win and lose criteria.
local SAVEGAME_VERSION = 189 -- Refactor earthquakes

class "App"

Expand Down
4 changes: 4 additions & 0 deletions CorsixTH/Lua/cheats.lua
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ function Cheats:cheatVip()
self.hospital:createVip()
end

function Cheats:cheatEarthquake()
return self.hospital.world.earthquake:createEarthquake()
end

function Cheats:cheatPatient()
self.hospital.world:spawnPatient()
end
Expand Down
237 changes: 237 additions & 0 deletions CorsixTH/Lua/earthquake.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
--[[ Copyright (c) 2024 Toby "tobylane"
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]

--! Manages all earthquakes in the world
class "Earthquake"

---@type Earthquake
local Earthquake = _G["Earthquake"]

-- All fields relating to the current or next earthquake:
-- active (boolean) If we are currently running the warning or damage timers
-- (after start_day of start_month is passed)
-- next_planned (boolean) If the next earthquake has been planned/generated
-- start_month (integer) The month the earthquake warning is triggered
-- start_day (integer) The day of the month the earthquake warning is triggered
-- size (integer) The amount of damage the earthquake causes (1-9)
-- remaining_damage (integer) The amount of damage this earthquake has yet to inflict
-- damage_timer (integer) The number of hours until the earthquake next inflicts damage if active
-- warning_timer (integer) The number of hours left until the real damaging earthquake begins
-- current_map_earthquake (integer) The number of earthquakes that have happened on this map so far

local damage_time = 16 -- Hours between each damage caused by an earthquake
local warning_period = 600 -- Hours between warning and real thing
local warning_length = 25 -- Length of early warning quake

local month_length = {
31, -- Jan
28, -- Feb (29 in leap years, but TH doesn't have leap years)
31, -- Mar
30, -- Apr
31, -- May
30, -- Jun
31, -- Jul
31, -- Aug
30, -- Sep
31, -- Oct
30, -- Nov
31, -- Dec
}

--! Track earthquakes in the world
--!param world (table) Owned by this world
--!param convert (boolean) If true, world is upgraded from before this Earthquake class
function Earthquake:Earthquake(world, convert)
self.world = world
if not convert then
self.current_map_earthquake = 0
self:nextEarthquake()
end
end

--! Perform actions to simulate an active earthquake.
function Earthquake:tick()
if not self.active then return end
local hosp = self.world:getLocalPlayerHospital()
-- Check if this is the day that the earthquake is supposed to stop
if self.remaining_damage == 0 then
self.active = false
hosp:tickEarthquake("end")
-- If the earthquake measured more than 7 on the richter scale, tell the user about it
if self.size > 7 then
hosp:giveAdvice({_A.earthquake.ended:format(math.floor(self.size))})
end

-- Set up the next earthquake date
self:nextEarthquake()
return
end

-- Start of warning quake
if self.warning_timer == warning_period then
self.next_planned = false
hosp:tickEarthquake("warning start")
end

-- End of warning quake
if self.warning_timer >= warning_period - warning_length and
self.warning_timer - self.world.hours_per_tick < warning_period - warning_length then
hosp:tickEarthquake("end")
end

if self.warning_timer > 0 then
self.warning_timer = self.warning_timer - self.world.hours_per_tick
-- Nothing more to do during inactive warning period
if self.warning_timer < warning_period - warning_length then
return
end

-- Start of real earthquake
if self.warning_timer <= 0 then
hosp:tickEarthquake("main start")
end
end

-- All earthquakes start and end small (small earthquakes never become
-- larger), so when there has been less than 2 damage applied or only
-- 2 damage remaining to be applied, move the screen with less
-- intensity than otherwise.
if self.remaining_damage <= 2 or
self.size - self.remaining_damage <= 2 then
hosp:tickEarthquake("small damage")
else
hosp:tickEarthquake("large damage")
end

hosp:tickEarthquake("sound")

-- Do not continue to damage phase while in a warning quake
if self.warning_timer > 0 then
return
end

-- Check if damage phase should go ahead
self.damage_timer = self.damage_timer - self.world.hours_per_tick
if self.damage_timer > 0 then return end

for _, room in pairs(self.world.rooms) do
for object, _ in pairs(room.objects) do
if object.strength then
object:machineUsed(room)
end
end
end

self.remaining_damage = self.remaining_damage - 1
self.damage_timer = self.damage_timer + damage_time

-- Patient falling: work in progress, see PR 1848
end

--! Check if it's time for an earthquake
function Earthquake:onEndDay()
if self.world.game_date:monthOfGame() == self.start_month and
self.world.game_date:dayOfMonth() == self.start_day then
-- Warn the user that an earthquake is on the way
self.active = true
end
end

--! Called when it is time to have another earthquake
function Earthquake:nextEarthquake()
self.active = false

local level_config = self.world.map.level_config
-- Check carefully that no value that we are going to use is going to be nil
if level_config.quake_control and level_config.quake_control[self.current_map_earthquake] and
level_config.quake_control[self.current_map_earthquake].Severity ~= 0 then
-- This map has rules to follow when making earthquakes, let's follow them
local control = level_config.quake_control[self.current_map_earthquake]
self.start_month = math.random(control.StartMonth, control.EndMonth)

-- Month length of the start of the earthquake. From start to finish
-- earthquakes do not persist for >= a month so we can wrap all days
-- after the start around the month length unambiguously.
local eqml = month_length[(self.start_month % 12) + 1]
self.start_day = math.random(1, eqml)

self.size = control.Severity
self.remaining_damage = self.size
self.damage_timer = damage_time
self.warning_timer = warning_period
self.current_map_earthquake = self.current_map_earthquake + 1
self.next_planned = true
end
end

--! The create an earthquake override from cheat menu
function Earthquake:createEarthquake()
-- Make sure an earthquake isn't already happening
if self.active then return false end

self.start_day = self.world.game_date:dayOfMonth()
self.start_month = self.world.game_date:monthOfGame()
if not self.next_planned then
-- Forcefully make an earthquake if there are none left in level file
self.size = math.random(1, 6) -- Above 6 seems disastrous
self.remaining_damage = self.size
self.damage_timer = damage_time
self.warning_timer = warning_period
end
end

--! Check if there is a current earthquake
--!return (boolean) True if there is a current earthquake
function Earthquake:isActive()
return self.active
end

--! Afterload sections before 189 were taken from World
--!param old (integer) The old version of the save game.
--!param new (integer) The current version of the save game format.
function Earthquake:afterLoad(old, new) -- luacheck: ignore 212 keep params from parent function
local old_quake = self.world.next_earthquake
if old < 115 then
if old_quake.active then
local rd = 0
for _, room in pairs(self.world.rooms) do
for object, _ in pairs(room.objects) do
if object.quake_points then
rd = math.max(rd, object.quake_points)
object.quake_points = nil
end
end
end
old_quake.remaining_damage = rd
old_quake.damage_timer = damage_time
old_quake.warning_timer = 0
end
end

if old < 189 then
if old_quake then
for k, v in pairs(old_quake) do
self[k] = v
end
else
self:nextEarthquake()
end
end
end
4 changes: 4 additions & 0 deletions CorsixTH/Lua/hospital.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2496,3 +2496,7 @@ end
--! Play a sound file
function Hospital:playSound(sound)
end

--! The UI parts of earthquake ticks
function Hospital:tickEarthquake(stage)
end
45 changes: 43 additions & 2 deletions CorsixTH/Lua/hospitals/player_hospital.lua
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,10 @@ end
--!param room The room of the machine
function PlayerHospital:announceRepair(room)
local sound = room.room_info.handyman_call_sound
local earthquake = self.world.next_earthquake
local earthquake = self.world.earthquake
self.world.ui:playAnnouncement("machwarn.wav", AnnouncementPriority.Critical)
-- If an earthquake is happening don't play the call sound to prevent spamming
if earthquake.active and earthquake.warning_timer == 0 then return end
if earthquake:isActive() and earthquake.warning_timer == 0 then return end
if self:countStaffOfCategory("Handyman", 1) == 0 then return end
if sound then self.world.ui:playAnnouncement(sound, AnnouncementPriority.Critical) end
end
Expand Down Expand Up @@ -774,6 +774,47 @@ function PlayerHospital:playSound(sound)
end
end

--! The UI parts of earthquake ticks
--!param stage (string) Stage of the active earthquake
function PlayerHospital:tickEarthquake(stage)
local ui = self.world.ui
local announcements = {
"quake001.wav", "quake002.wav", "quake003.wav", "quake004.wav",
}

-- Start of earhquakes
if stage == "warning start" then
ui:beginShakeScreen(0.2)
ui:playRandomAnnouncement(announcements, AnnouncementPriority.Critical)

elseif stage == "main start" then
ui:playRandomAnnouncement(announcements, AnnouncementPriority.Critical)

-- At the end of the warning or main earthquake, stop all screen movement
elseif stage == "end" then
ui:endShakeScreen()

-- All earthquakes start and end small (small earthquakes never become
-- larger), so when there has been less than 2 damage applied or only
-- 2 damage remaining to be applied, move the screen with less
-- intensity than otherwise.
elseif stage == "small damage" then
ui:beginShakeScreen(0.5)
elseif stage == "large damage" then
ui:beginShakeScreen(1)

elseif stage == "sound" then
-- Play the earthquake sound. It has different names depending on what the language contains.
if self.world.app.audio:soundExists("quake2.wav") then
ui:playSound("quake2.wav")
else
ui:playSound("quake.wav")
end
else
print("Unknown stage: " .. stage)
end
end

function PlayerHospital:afterLoad(old, new)
if old < 146 then
self.adviser_data = {
Expand Down

0 comments on commit 7d8fe7f

Please sign in to comment.