mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-25 04:57:12 +01:00
Merge branch 'master' into diagonal-movement
This commit is contained in:
@@ -1,98 +0,0 @@
|
||||
/var/lighting_overlays_initialised = FALSE
|
||||
|
||||
/var/list/lighting_update_lights = list() // List of lighting sources queued for update.
|
||||
/var/list/lighting_update_corners = list() // List of lighting corners queued for update.
|
||||
/var/list/lighting_update_overlays = list() // List of lighting overlays queued for update.
|
||||
|
||||
/var/list/lighting_update_lights_old = list() // List of lighting sources currently being updated.
|
||||
/var/list/lighting_update_corners_old = list() // List of lighting corners currently being updated.
|
||||
/var/list/lighting_update_overlays_old = list() // List of lighting overlays currently being updated.
|
||||
|
||||
|
||||
/datum/controller/process/lighting
|
||||
// Queues of update counts, waiting to be rolled into stats lists
|
||||
var/list/stats_queues = list(
|
||||
"Source" = list(), "Corner" = list(), "Overlay" = list())
|
||||
// Stats lists
|
||||
var/list/stats_lists = list(
|
||||
"Source" = list(), "Corner" = list(), "Overlay" = list())
|
||||
var/update_stats_every = (1 SECONDS)
|
||||
var/next_stats_update = 0
|
||||
var/stat_updates_to_keep = 5
|
||||
|
||||
/datum/controller/process/lighting/setup()
|
||||
name = "lighting"
|
||||
|
||||
schedule_interval = 0 // run as fast as you possibly can
|
||||
sleep_interval = 10 // Yield every 10% of a tick
|
||||
defer_usage = 80 // Defer at 80% of a tick
|
||||
create_all_lighting_overlays()
|
||||
lighting_overlays_initialised = TRUE
|
||||
|
||||
// Pre-process lighting once before the round starts. Wait 30 seconds so the away mission has time to load.
|
||||
spawn(300)
|
||||
doWork(1)
|
||||
|
||||
/datum/controller/process/lighting/doWork(roundstart)
|
||||
|
||||
lighting_update_lights_old = lighting_update_lights //We use a different list so any additions to the update lists during a delay from scheck() don't cause things to be cut from the list without being updated.
|
||||
lighting_update_lights = list()
|
||||
for(var/datum/light_source/L in lighting_update_lights_old)
|
||||
|
||||
if(L.check() || L.destroyed || L.force_update)
|
||||
L.remove_lum()
|
||||
if(!L.destroyed)
|
||||
L.apply_lum()
|
||||
|
||||
else if(L.vis_update) //We smartly update only tiles that became (in) visible to use.
|
||||
L.smart_vis_update()
|
||||
|
||||
L.vis_update = FALSE
|
||||
L.force_update = FALSE
|
||||
L.needs_update = FALSE
|
||||
|
||||
SCHECK
|
||||
|
||||
lighting_update_corners_old = lighting_update_corners //Same as above.
|
||||
lighting_update_corners = list()
|
||||
for(var/A in lighting_update_corners_old)
|
||||
var/datum/lighting_corner/C = A
|
||||
|
||||
C.update_overlays()
|
||||
|
||||
C.needs_update = FALSE
|
||||
|
||||
SCHECK
|
||||
|
||||
lighting_update_overlays_old = lighting_update_overlays //Same as above.
|
||||
lighting_update_overlays = list()
|
||||
|
||||
for(var/A in lighting_update_overlays_old)
|
||||
var/atom/movable/lighting_overlay/O = A
|
||||
O.update_overlay()
|
||||
O.needs_update = 0
|
||||
SCHECK
|
||||
|
||||
stats_queues["Source"] += lighting_update_lights_old.len
|
||||
stats_queues["Corner"] += lighting_update_corners_old.len
|
||||
stats_queues["Overlay"] += lighting_update_overlays_old.len
|
||||
|
||||
if(next_stats_update <= world.time)
|
||||
next_stats_update = world.time + update_stats_every
|
||||
for(var/stat_name in stats_queues)
|
||||
var/stat_sum = 0
|
||||
var/list/stats_queue = stats_queues[stat_name]
|
||||
for(var/count in stats_queue)
|
||||
stat_sum += count
|
||||
stats_queue.Cut()
|
||||
|
||||
var/list/stats_list = stats_lists[stat_name]
|
||||
stats_list.Insert(1, stat_sum)
|
||||
if(stats_list.len > stat_updates_to_keep)
|
||||
stats_list.Cut(stats_list.len)
|
||||
|
||||
/datum/controller/process/lighting/statProcess()
|
||||
..()
|
||||
stat(null, "[total_lighting_sources] sources, [total_lighting_corners] corners, [total_lighting_overlays] overlays")
|
||||
for(var/stat_type in stats_lists)
|
||||
stat(null, "[stat_type] updates: [jointext(stats_lists[stat_type], " | ")]")
|
||||
@@ -1,36 +0,0 @@
|
||||
var/global/datum/controller/process/ticker/tickerProcess
|
||||
|
||||
/datum/controller/process/ticker
|
||||
var/lastTickerTimeDuration
|
||||
var/lastTickerTime
|
||||
|
||||
/datum/controller/process/ticker/setup()
|
||||
name = "ticker"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
|
||||
lastTickerTime = world.timeofday
|
||||
log_startup_progress("Time ticker starting up.")
|
||||
|
||||
if(!ticker)
|
||||
ticker = new
|
||||
|
||||
spawn(0)
|
||||
if(ticker)
|
||||
ticker.pregame()
|
||||
|
||||
DECLARE_GLOBAL_CONTROLLER(ticker, tickerProcess)
|
||||
|
||||
/datum/controller/process/ticker/doWork()
|
||||
var/currentTime = world.timeofday
|
||||
|
||||
if(currentTime < lastTickerTime) // check for midnight rollover
|
||||
lastTickerTimeDuration = (currentTime - (lastTickerTime - TICKS_IN_DAY)) / TICKS_IN_SECOND
|
||||
else
|
||||
lastTickerTimeDuration = (currentTime - lastTickerTime) / TICKS_IN_SECOND
|
||||
|
||||
lastTickerTime = currentTime
|
||||
|
||||
ticker.process_decrepit()
|
||||
|
||||
/datum/controller/process/ticker/proc/getLastTickerTimeDuration()
|
||||
return lastTickerTimeDuration
|
||||
@@ -605,8 +605,7 @@
|
||||
config.event_delay_upper[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3])
|
||||
|
||||
if("starlight")
|
||||
var/vvalue = text2num(value)
|
||||
config.starlight = vvalue >= 0 ? vvalue : 0
|
||||
config.starlight = 1
|
||||
|
||||
if("player_reroute_cap")
|
||||
var/vvalue = text2num(value)
|
||||
@@ -787,7 +786,7 @@
|
||||
config.sql_enabled = 0
|
||||
log_config("WARNING: DB_CONFIG DEFINITION MISMATCH!")
|
||||
spawn(60)
|
||||
if(ticker.current_state == GAME_STATE_PREGAME)
|
||||
if(SSticker.current_state == GAME_STATE_PREGAME)
|
||||
going = 0
|
||||
spawn(600)
|
||||
to_chat(world, "<span class='alert'>DB_CONFIG MISMATCH, ROUND START DELAYED. <BR>Please check database version for recent upstream changes!</span>")
|
||||
|
||||
@@ -9,114 +9,3 @@
|
||||
* Called in gameticker.dm when a round starts.
|
||||
*/
|
||||
/hook/roundstart
|
||||
|
||||
/**
|
||||
* Roundend hook.
|
||||
* Called in gameticker.dm when a round ends.
|
||||
*/
|
||||
/hook/roundend
|
||||
|
||||
/**
|
||||
* LateSpawn hook.
|
||||
* Called in newplayer.dm when a humanoid character joins the round after it started.
|
||||
* Parameters: var/mob/living/carbon/human
|
||||
*/
|
||||
/hook/latespawn
|
||||
|
||||
/**
|
||||
* Death hook.
|
||||
* Called in death.dm when someone dies.
|
||||
* Parameters: var/mob/living/carbon/human, var/gibbed
|
||||
*/
|
||||
/hook/death
|
||||
|
||||
/**
|
||||
* Cloning hook.
|
||||
* Called in cloning.dm when someone is brought back by the wonders of modern science.
|
||||
* Parameters: var/mob/living/carbon/human
|
||||
*/
|
||||
/hook/clone
|
||||
|
||||
/**
|
||||
* Debrained hook.
|
||||
* Called in brain_item.dm when someone gets debrained.
|
||||
* Parameters: var/obj/item/brain
|
||||
*/
|
||||
/hook/debrain
|
||||
|
||||
/**
|
||||
* Borged hook.
|
||||
* Called in robot_parts.dm when someone gets turned into a cyborg.
|
||||
* Parameters: var/mob/living/silicon/robot
|
||||
*/
|
||||
/hook/borgify
|
||||
|
||||
/**
|
||||
* Podman hook.
|
||||
* Called in podmen.dm when someone is brought back as a Diona.
|
||||
* Parameters: var/mob/living/carbon/primitive/diona
|
||||
*/
|
||||
/hook/harvest_podman
|
||||
|
||||
/**
|
||||
* Payroll revoked hook.
|
||||
* Called in Accounts_DB.dm when someone's payroll is stolen at the Accounts terminal.
|
||||
* Parameters: var/datum/money_account
|
||||
*/
|
||||
/hook/revoke_payroll
|
||||
|
||||
/**
|
||||
* Account suspension hook.
|
||||
* Called in Accounts_DB.dm when someone's account is suspended or unsuspended at the Accounts terminal.
|
||||
* Parameters: var/datum/money_account
|
||||
*/
|
||||
/hook/change_account_status
|
||||
|
||||
/**
|
||||
* Employee reassignment hook.
|
||||
* Called in card.dm when someone's card is reassigned at the HoP's desk.
|
||||
* Parameters: var/obj/item/card/id
|
||||
*/
|
||||
/hook/reassign_employee
|
||||
|
||||
/**
|
||||
* Employee terminated hook.
|
||||
* Called in card.dm when someone's card is terminated at the HoP's desk.
|
||||
* Parameters: var/obj/item/card/id
|
||||
*/
|
||||
/hook/terminate_employee
|
||||
|
||||
/**
|
||||
* Crate sold hook.
|
||||
* Called in supplyshuttle.dm when a crate is sold on the shuttle.
|
||||
* Parameters: var/obj/structure/closet/crate/sold, var/area/shuttle
|
||||
*/
|
||||
/hook/sell_crate
|
||||
|
||||
/**
|
||||
* Captain spawned hook.
|
||||
* Called in supervisor.dm when a captain spawns
|
||||
* Parameters: var/mob/living/carbon/human/captain
|
||||
*/
|
||||
/hook/captain_spawned
|
||||
|
||||
/**
|
||||
* Mob login hook.
|
||||
* Called in login.dm when a player logs in to a mob.
|
||||
* Parameters: var/client/client, var/mob/mob
|
||||
*/
|
||||
/hook/mob_login
|
||||
|
||||
/**
|
||||
* Mob logout hook.
|
||||
* Called in logout.dm when a player logs out of a mob.
|
||||
* Parameters: var/client/client, var/mob/mob
|
||||
*/
|
||||
/hook/mob_logout
|
||||
|
||||
/**
|
||||
* Mob area change hook.
|
||||
* Called in area.dm when a mob moves from one area to another.
|
||||
* Parameters: var/mob/mob, var/area/newarea, var/area/oldarea
|
||||
*/
|
||||
/hook/mob_area_change
|
||||
|
||||
@@ -194,8 +194,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
|
||||
to_chat(world, "<span class='boldannounce'>[msg]</span>")
|
||||
log_world(msg)
|
||||
|
||||
if(config.developer_express_start & ticker.current_state == GAME_STATE_PREGAME)
|
||||
ticker.current_state = GAME_STATE_SETTING_UP
|
||||
if(config.developer_express_start & SSticker.current_state == GAME_STATE_PREGAME)
|
||||
SSticker.current_state = GAME_STATE_SETTING_UP
|
||||
|
||||
if(!current_runlevel)
|
||||
SetRunLevel(1)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
SUBSYSTEM_DEF(jobs)
|
||||
name = "Jobs"
|
||||
init_order = INIT_ORDER_JOBS // 12
|
||||
flags = SS_NO_FIRE
|
||||
wait = 3000 // 5 minutes (Deciseconds)
|
||||
runlevels = RUNLEVEL_GAME
|
||||
|
||||
//List of all jobs
|
||||
var/list/occupations = list()
|
||||
@@ -21,6 +22,12 @@ SUBSYSTEM_DEF(jobs)
|
||||
LoadJobs("config/jobs.txt")
|
||||
return ..()
|
||||
|
||||
// Only fires every 5 minutes
|
||||
/datum/controller/subsystem/jobs/fire()
|
||||
if(!config.sql_enabled || !config.use_exp_tracking)
|
||||
return
|
||||
update_exp(5,0)
|
||||
|
||||
/datum/controller/subsystem/jobs/proc/SetupOccupations(var/list/faction = list("Station"))
|
||||
occupations = list()
|
||||
var/list/all_jobs = subtypesof(/datum/job)
|
||||
@@ -270,9 +277,9 @@ SUBSYSTEM_DEF(jobs)
|
||||
SetupOccupations()
|
||||
|
||||
//Holder for Triumvirate is stored in the ticker, this just processes it
|
||||
if(ticker)
|
||||
if(SSticker)
|
||||
for(var/datum/job/ai/A in occupations)
|
||||
if(ticker.triai)
|
||||
if(SSticker.triai)
|
||||
A.spawn_positions = 3
|
||||
|
||||
//Get the players who are ready
|
||||
@@ -473,10 +480,10 @@ SUBSYSTEM_DEF(jobs)
|
||||
T = S.loc
|
||||
|
||||
if(T)
|
||||
H.loc = T
|
||||
H.forceMove(T)
|
||||
// Moving wheelchair if they have one
|
||||
if(H.buckled && istype(H.buckled, /obj/structure/chair/wheelchair))
|
||||
H.buckled.loc = H.loc
|
||||
H.buckled.forceMove(H.loc)
|
||||
H.buckled.dir = H.dir
|
||||
|
||||
if(job)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
GLOBAL_LIST_EMPTY(lighting_update_lights) // List of lighting sources queued for update.
|
||||
GLOBAL_LIST_EMPTY(lighting_update_corners) // List of lighting corners queued for update.
|
||||
GLOBAL_LIST_EMPTY(lighting_update_objects) // List of lighting objects queued for update.
|
||||
|
||||
SUBSYSTEM_DEF(lighting)
|
||||
name = "Lighting"
|
||||
wait = 2
|
||||
init_order = INIT_ORDER_LIGHTING
|
||||
flags = SS_TICKER
|
||||
|
||||
/datum/controller/subsystem/lighting/stat_entry()
|
||||
..("L:[GLOB.lighting_update_lights.len]|C:[GLOB.lighting_update_corners.len]|O:[GLOB.lighting_update_objects.len]")
|
||||
|
||||
/datum/controller/subsystem/lighting/Initialize(timeofday)
|
||||
if(!initialized)
|
||||
if(config.starlight)
|
||||
for(var/I in GLOB.all_areas)
|
||||
var/area/A = I
|
||||
if(A.dynamic_lighting == DYNAMIC_LIGHTING_IFSTARLIGHT)
|
||||
A.luminosity = 0
|
||||
|
||||
create_all_lighting_objects()
|
||||
initialized = TRUE
|
||||
|
||||
fire(FALSE, TRUE)
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/lighting/fire(resumed, init_tick_checks)
|
||||
MC_SPLIT_TICK_INIT(3)
|
||||
if(!init_tick_checks)
|
||||
MC_SPLIT_TICK
|
||||
var/i = 0
|
||||
for(i in 1 to GLOB.lighting_update_lights.len)
|
||||
var/datum/light_source/L = GLOB.lighting_update_lights[i]
|
||||
|
||||
L.update_corners()
|
||||
|
||||
L.needs_update = LIGHTING_NO_UPDATE
|
||||
|
||||
if(init_tick_checks)
|
||||
CHECK_TICK
|
||||
else if(MC_TICK_CHECK)
|
||||
break
|
||||
if(i)
|
||||
GLOB.lighting_update_lights.Cut(1, i+1)
|
||||
i = 0
|
||||
|
||||
if(!init_tick_checks)
|
||||
MC_SPLIT_TICK
|
||||
|
||||
for (i in 1 to GLOB.lighting_update_corners.len)
|
||||
var/datum/lighting_corner/C = GLOB.lighting_update_corners[i]
|
||||
|
||||
C.update_objects()
|
||||
C.needs_update = FALSE
|
||||
if(init_tick_checks)
|
||||
CHECK_TICK
|
||||
else if(MC_TICK_CHECK)
|
||||
break
|
||||
if(i)
|
||||
GLOB.lighting_update_corners.Cut(1, i+1)
|
||||
i = 0
|
||||
|
||||
|
||||
if(!init_tick_checks)
|
||||
MC_SPLIT_TICK
|
||||
|
||||
for (i in 1 to GLOB.lighting_update_objects.len)
|
||||
var/atom/movable/lighting_object/O = GLOB.lighting_update_objects[i]
|
||||
|
||||
if(QDELETED(O))
|
||||
continue
|
||||
|
||||
O.update()
|
||||
O.needs_update = FALSE
|
||||
if(init_tick_checks)
|
||||
CHECK_TICK
|
||||
else if(MC_TICK_CHECK)
|
||||
break
|
||||
if(i)
|
||||
GLOB.lighting_update_objects.Cut(1, i+1)
|
||||
|
||||
|
||||
/datum/controller/subsystem/lighting/Recover()
|
||||
initialized = SSlighting.initialized
|
||||
..()
|
||||
@@ -20,7 +20,7 @@ SUBSYSTEM_DEF(nightshift)
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/nightshift/fire(resumed = FALSE)
|
||||
if(world.time - round_start_time < nightshift_first_check)
|
||||
if(world.time - SSticker.round_start_time < nightshift_first_check)
|
||||
return
|
||||
check_nightshift()
|
||||
|
||||
|
||||
@@ -106,8 +106,8 @@ SUBSYSTEM_DEF(shuttle)
|
||||
return
|
||||
emergency = backup_shuttle
|
||||
|
||||
if(world.time - round_start_time < config.shuttle_refuel_delay)
|
||||
to_chat(user, "The emergency shuttle is refueling. Please wait another [abs(round(((world.time - round_start_time) - config.shuttle_refuel_delay)/600))] minutes before trying again.")
|
||||
if(world.time - SSticker.round_start_time < config.shuttle_refuel_delay)
|
||||
to_chat(user, "The emergency shuttle is refueling. Please wait another [abs(round(((world.time - SSticker.round_start_time) - config.shuttle_refuel_delay)/600))] minutes before trying again.")
|
||||
return
|
||||
|
||||
switch(emergency.mode)
|
||||
@@ -169,7 +169,7 @@ SUBSYSTEM_DEF(shuttle)
|
||||
return
|
||||
if(!emergency.canRecall)
|
||||
return
|
||||
if(ticker.mode.name == "meteor")
|
||||
if(SSticker.mode.name == "meteor")
|
||||
return
|
||||
if(seclevel2num(get_security_level()) >= SEC_LEVEL_RED)
|
||||
if(emergency.timeLeft(1) < emergencyCallTime * 0.25)
|
||||
|
||||
@@ -0,0 +1,533 @@
|
||||
SUBSYSTEM_DEF(ticker)
|
||||
name = "Ticker"
|
||||
init_order = INIT_ORDER_TICKER
|
||||
|
||||
priority = FIRE_PRIORITY_TICKER
|
||||
flags = SS_KEEP_TIMING
|
||||
runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME
|
||||
|
||||
var/round_start_time = 0
|
||||
var/const/restart_timeout = 600
|
||||
var/current_state = GAME_STATE_STARTUP
|
||||
var/force_start = 0 // Do we want to force-start as soon as we can
|
||||
var/force_ending = 0
|
||||
var/hide_mode = 0 // leave here at 0 ! setup() will take care of it when needed for Secret mode -walter0o
|
||||
var/datum/game_mode/mode = null
|
||||
var/event_time = null
|
||||
var/event = 0
|
||||
var/login_music // music played in pregame lobby
|
||||
var/list/datum/mind/minds = list()//The people in the game. Used for objective tracking.
|
||||
var/Bible_icon_state // icon_state the chaplain has chosen for his bible
|
||||
var/Bible_item_state // item_state the chaplain has chosen for his bible
|
||||
var/Bible_name // name of the bible
|
||||
var/Bible_deity_name
|
||||
var/datum/cult_info/cultdat = null //here instead of cult for adminbus purposes
|
||||
var/random_players = 0 // if set to nonzero, ALL players who latejoin or declare-ready join will have random appearances/genders
|
||||
var/list/syndicate_coalition = list() // list of traitor-compatible factions
|
||||
var/list/factions = list() // list of all factions
|
||||
var/list/availablefactions = list() // list of factions with openings
|
||||
var/tipped = FALSE //Did we broadcast the tip of the day yet?
|
||||
var/selected_tip // What will be the tip of the day?
|
||||
var/pregame_timeleft // This is used for calculations
|
||||
var/delay_end = 0 //if set to nonzero, the round will not restart on it's own
|
||||
var/triai = 0//Global holder for Triumvirate
|
||||
var/initialtpass = 0 //holder for inital autotransfer vote timer
|
||||
var/obj/screen/cinematic = null //used for station explosion cinematic
|
||||
var/round_end_announced = 0 // Spam Prevention. Announce round end only once.\
|
||||
|
||||
/datum/controller/subsystem/ticker/Initialize()
|
||||
login_music = pick(\
|
||||
'sound/music/thunderdome.ogg',\
|
||||
'sound/music/space.ogg',\
|
||||
'sound/music/title1.ogg',\
|
||||
'sound/music/title2.ogg',\
|
||||
'sound/music/title3.ogg',)
|
||||
// Setup codephrase
|
||||
if(!GLOB.syndicate_code_phrase)
|
||||
GLOB.syndicate_code_phrase = generate_code_phrase()
|
||||
if(!GLOB.syndicate_code_response)
|
||||
GLOB.syndicate_code_response = generate_code_phrase()
|
||||
|
||||
// Map name
|
||||
if(using_map && using_map.name)
|
||||
GLOB.map_name = "[using_map.name]"
|
||||
else
|
||||
GLOB.map_name = "Unknown"
|
||||
|
||||
// World name
|
||||
if(config && config.server_name)
|
||||
world.name = "[config.server_name]: [station_name()]"
|
||||
else
|
||||
world.name = station_name()
|
||||
|
||||
return ..()
|
||||
|
||||
|
||||
/datum/controller/subsystem/ticker/fire()
|
||||
switch(current_state)
|
||||
if(GAME_STATE_STARTUP)
|
||||
// This is ran as soon as the MC starts firing, and should only run ONCE, unless startup fails
|
||||
round_start_time = world.time + (config.pregame_timestart * 10)
|
||||
to_chat(world, "<B><FONT color='blue'>Welcome to the pre-game lobby!</FONT></B>")
|
||||
to_chat(world, "Please, setup your character and select ready. Game will start in [config.pregame_timestart] seconds")
|
||||
current_state = GAME_STATE_PREGAME
|
||||
fire() // TG says this is a good idea
|
||||
if(GAME_STATE_PREGAME)
|
||||
// This is so we dont have sleeps in controllers, because that is a bad, bad thing
|
||||
pregame_timeleft = max(0,round_start_time - world.time)
|
||||
|
||||
if(pregame_timeleft <= 600 && !tipped) // 60 seconds
|
||||
send_tip_of_the_round()
|
||||
tipped = TRUE
|
||||
|
||||
if(pregame_timeleft <= 0 || force_start)
|
||||
current_state = GAME_STATE_SETTING_UP
|
||||
Master.SetRunLevel(RUNLEVEL_SETUP)
|
||||
if(GAME_STATE_SETTING_UP)
|
||||
if(!setup()) // Setup failed
|
||||
current_state = GAME_STATE_STARTUP
|
||||
Master.SetRunLevel(RUNLEVEL_LOBBY)
|
||||
if(GAME_STATE_PLAYING)
|
||||
mode.process()
|
||||
mode.process_job_tasks()
|
||||
var/game_finished = SSshuttle.emergency.mode >= SHUTTLE_ENDGAME || mode.station_was_nuked
|
||||
if(config.continuous_rounds)
|
||||
mode.check_finished() // some modes contain var-changing code in here, so call even if we don't uses result
|
||||
else
|
||||
game_finished |= mode.check_finished()
|
||||
if(game_finished)
|
||||
current_state = GAME_STATE_FINISHED
|
||||
if(GAME_STATE_FINISHED)
|
||||
current_state = GAME_STATE_FINISHED
|
||||
Master.SetRunLevel(RUNLEVEL_POSTGAME) // This shouldnt process more than once, but you never know
|
||||
auto_toggle_ooc(1) // Turn it on
|
||||
|
||||
spawn(0)
|
||||
declare_completion()
|
||||
|
||||
spawn(50)
|
||||
if(mode.station_was_nuked)
|
||||
world.Reboot("Station destroyed by Nuclear Device.", "end_proper", "nuke")
|
||||
else
|
||||
world.Reboot("Round ended.", "end_proper", "proper completion")
|
||||
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/votetimer()
|
||||
var/timerbuffer = 0
|
||||
if(initialtpass == 0)
|
||||
timerbuffer = config.vote_autotransfer_initial
|
||||
else
|
||||
timerbuffer = config.vote_autotransfer_interval
|
||||
spawn(timerbuffer)
|
||||
SSvote.autotransfer()
|
||||
initialtpass = 1
|
||||
votetimer()
|
||||
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/setup()
|
||||
cultdat = setupcult()
|
||||
//Create and announce mode
|
||||
if(master_mode=="secret")
|
||||
src.hide_mode = 1
|
||||
var/list/datum/game_mode/runnable_modes
|
||||
if((master_mode=="random") || (master_mode=="secret"))
|
||||
runnable_modes = config.get_runnable_modes()
|
||||
if(runnable_modes.len==0)
|
||||
current_state = GAME_STATE_PREGAME
|
||||
Master.SetRunLevel(RUNLEVEL_LOBBY)
|
||||
to_chat(world, "<B>Unable to choose playable game mode.</B> Reverting to pre-game lobby.")
|
||||
return 0
|
||||
if(secret_force_mode != "secret")
|
||||
var/datum/game_mode/M = config.pick_mode(secret_force_mode)
|
||||
if(M.can_start())
|
||||
src.mode = config.pick_mode(secret_force_mode)
|
||||
SSjobs.ResetOccupations()
|
||||
if(!src.mode)
|
||||
src.mode = pickweight(runnable_modes)
|
||||
if(src.mode)
|
||||
var/mtype = src.mode.type
|
||||
src.mode = new mtype
|
||||
else
|
||||
src.mode = config.pick_mode(master_mode)
|
||||
if(!src.mode.can_start())
|
||||
to_chat(world, "<B>Unable to start [mode.name].</B> Not enough players, [mode.required_players] players needed. Reverting to pre-game lobby.")
|
||||
mode = null
|
||||
current_state = GAME_STATE_PREGAME
|
||||
SSjobs.ResetOccupations()
|
||||
Master.SetRunLevel(RUNLEVEL_LOBBY)
|
||||
return 0
|
||||
|
||||
//Configure mode and assign player to special mode stuff
|
||||
src.mode.pre_pre_setup()
|
||||
var/can_continue
|
||||
can_continue = src.mode.pre_setup()//Setup special modes
|
||||
SSjobs.DivideOccupations() //Distribute jobs
|
||||
if(!can_continue)
|
||||
qdel(mode)
|
||||
current_state = GAME_STATE_PREGAME
|
||||
to_chat(world, "<B>Error setting up [master_mode].</B> Reverting to pre-game lobby.")
|
||||
SSjobs.ResetOccupations()
|
||||
Master.SetRunLevel(RUNLEVEL_LOBBY)
|
||||
return 0
|
||||
|
||||
if(hide_mode)
|
||||
var/list/modes = new
|
||||
for(var/datum/game_mode/M in runnable_modes)
|
||||
modes+=M.name
|
||||
modes = sortList(modes)
|
||||
to_chat(world, "<B>The current game mode is - Secret!</B>")
|
||||
to_chat(world, "<B>Possibilities:</B> [english_list(modes)]")
|
||||
else
|
||||
src.mode.announce()
|
||||
|
||||
create_characters() //Create player characters and transfer them
|
||||
populate_spawn_points()
|
||||
collect_minds()
|
||||
equip_characters()
|
||||
data_core.manifest()
|
||||
current_state = GAME_STATE_PLAYING
|
||||
Master.SetRunLevel(RUNLEVEL_GAME)
|
||||
|
||||
callHook("roundstart")
|
||||
|
||||
//here to initialize the random events nicely at round start
|
||||
setup_economy()
|
||||
setupfactions()
|
||||
|
||||
//shuttle_controller.setup_shuttle_docks()
|
||||
|
||||
spawn(0)//Forking here so we dont have to wait for this to finish
|
||||
mode.post_setup()
|
||||
//Cleanup some stuff
|
||||
for(var/obj/effect/landmark/start/S in GLOB.landmarks_list)
|
||||
//Deleting Startpoints but we need the ai point to AI-ize people later
|
||||
if(S.name != "AI")
|
||||
qdel(S)
|
||||
|
||||
// take care of random spesspod spawning
|
||||
var/list/obj/effect/landmark/spacepod/random/L = list()
|
||||
for(var/obj/effect/landmark/spacepod/random/SS in GLOB.landmarks_list)
|
||||
if(istype(SS))
|
||||
L += SS
|
||||
if(L.len)
|
||||
var/obj/effect/landmark/spacepod/random/S = pick(L)
|
||||
new /obj/spacepod/random(S.loc)
|
||||
for(var/obj/effect/landmark/spacepod/random/R in L)
|
||||
qdel(R)
|
||||
|
||||
to_chat(world, "<FONT color='blue'><B>Enjoy the game!</B></FONT>")
|
||||
world << sound('sound/AI/welcome.ogg')// Skie
|
||||
|
||||
if(SSholiday.holidays)
|
||||
to_chat(world, "<font color='blue'>and...</font>")
|
||||
for(var/holidayname in SSholiday.holidays)
|
||||
var/datum/holiday/holiday = SSholiday.holidays[holidayname]
|
||||
to_chat(world, "<h4>[holiday.greet()]</h4>")
|
||||
|
||||
spawn(0) // Forking dynamic room selection
|
||||
var/list/area/dynamic/source/available_source_candidates = subtypesof(/area/dynamic/source)
|
||||
var/list/area/dynamic/destination/available_destination_candidates = subtypesof(/area/dynamic/destination)
|
||||
|
||||
for(var/area/dynamic/destination/current_destination_candidate in available_destination_candidates)
|
||||
var/area/dynamic/destination/current_destination = locate(current_destination_candidate)
|
||||
|
||||
if(!current_destination)
|
||||
continue
|
||||
|
||||
if(current_destination.match_width == 0 || current_destination.match_height == 0)
|
||||
message_admins("Dynamic area destination '[current_destination.name]' does not have its size requirements set.")
|
||||
continue
|
||||
|
||||
var/list/area/dynamic/source/candidate_source_areas = new /list(0)
|
||||
for(var/area/dynamic/source/candidate_source_area in available_source_candidates)
|
||||
var/area/dynamic/source/candidate_source = locate(candidate_source_area)
|
||||
|
||||
if(!candidate_source)
|
||||
continue
|
||||
|
||||
if(candidate_source.match_tag != current_destination.match_tag)
|
||||
continue
|
||||
|
||||
if(candidate_source.match_width != current_destination.match_width || \
|
||||
candidate_source.match_height != current_destination.match_height)
|
||||
continue
|
||||
|
||||
candidate_source_areas += candidate_source
|
||||
|
||||
if(candidate_source_areas.len == 0)
|
||||
message_admins("Failed to find a matching source for dynamic area: [current_destination.name]")
|
||||
continue
|
||||
|
||||
var/area/dynamic/source/selected_source = pick(candidate_source_areas)
|
||||
available_source_candidates -= selected_source
|
||||
|
||||
selected_source.copy_contents_to(current_destination, 0)
|
||||
|
||||
if(current_destination.enable_lights || selected_source.enable_lights)
|
||||
current_destination.power_light = 1
|
||||
else
|
||||
current_destination.power_light = 0
|
||||
current_destination.power_change()
|
||||
|
||||
//start_events() //handles random events and space dust.
|
||||
//new random event system is handled from the MC.
|
||||
|
||||
var/list/admins_number = staff_countup(R_BAN)
|
||||
if(admins_number[1] == 0 && admins_number[3] == 0)
|
||||
send2irc(config.admin_notify_irc, "Round has started with no admins online.")
|
||||
auto_toggle_ooc(0) // Turn it off
|
||||
round_start_time = world.time
|
||||
|
||||
/* DONE THROUGH PROCESS SCHEDULER
|
||||
supply_controller.process() //Start the supply shuttle regenerating points -- TLE
|
||||
master_controller.process() //Start master_controller.process()
|
||||
lighting_controller.process() //Start processing DynamicAreaLighting updates
|
||||
*/
|
||||
|
||||
processScheduler.start()
|
||||
|
||||
if(config.sql_enabled)
|
||||
spawn(3000)
|
||||
statistic_cycle() // Polls population totals regularly and stores them in an SQL DB
|
||||
|
||||
votetimer()
|
||||
|
||||
for(var/mob/new_player/N in GLOB.mob_list)
|
||||
if(N.client)
|
||||
N.new_player_panel_proc()
|
||||
|
||||
return 1
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(station_missed = 0, override = null)
|
||||
if(cinematic)
|
||||
return //already a cinematic in progress!
|
||||
|
||||
auto_toggle_ooc(1) // Turn it on
|
||||
//initialise our cinematic screen object
|
||||
cinematic = new /obj/screen(src)
|
||||
cinematic.icon = 'icons/effects/station_explosion.dmi'
|
||||
cinematic.icon_state = "station_intact"
|
||||
cinematic.layer = 21
|
||||
cinematic.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
cinematic.screen_loc = "1,0"
|
||||
|
||||
var/obj/structure/bed/temp_buckle = new(src)
|
||||
if(station_missed)
|
||||
for(var/mob/M in GLOB.mob_list)
|
||||
M.buckled = temp_buckle //buckles the mob so it can't do anything
|
||||
if(M.client)
|
||||
M.client.screen += cinematic //show every client the cinematic
|
||||
else //nuke kills everyone on z-level 1 to prevent "hurr-durr I survived"
|
||||
for(var/mob/M in GLOB.mob_list)
|
||||
M.buckled = temp_buckle
|
||||
if(M.stat != DEAD)
|
||||
var/turf/T = get_turf(M)
|
||||
if(T && is_station_level(T.z) && !istype(M.loc, /obj/structure/closet/secure_closet/freezer))
|
||||
var/mob/ghost = M.ghostize()
|
||||
M.dust() //no mercy
|
||||
if(ghost && ghost.client) //Play the victims an uninterrupted cinematic.
|
||||
ghost.client.screen += cinematic
|
||||
CHECK_TICK
|
||||
if(M && M.client) //Play the survivors a cinematic.
|
||||
M.client.screen += cinematic
|
||||
|
||||
//Now animate the cinematic
|
||||
switch(station_missed)
|
||||
if(1) //nuke was nearby but (mostly) missed
|
||||
if(mode && !override)
|
||||
override = mode.name
|
||||
switch(override)
|
||||
if("nuclear emergency") //Nuke wasn't on station when it blew up
|
||||
flick("intro_nuke", cinematic)
|
||||
sleep(35)
|
||||
world << sound('sound/effects/explosionfar.ogg')
|
||||
flick("station_intact_fade_red", cinematic)
|
||||
cinematic.icon_state = "summary_nukefail"
|
||||
if("fake") //The round isn't over, we're just freaking people out for fun
|
||||
flick("intro_nuke", cinematic)
|
||||
sleep(35)
|
||||
world << sound('sound/items/bikehorn.ogg')
|
||||
flick("summary_selfdes", cinematic)
|
||||
else
|
||||
flick("intro_nuke", cinematic)
|
||||
sleep(35)
|
||||
world << sound('sound/effects/explosionfar.ogg')
|
||||
|
||||
|
||||
if(2) //nuke was nowhere nearby //TODO: a really distant explosion animation
|
||||
sleep(50)
|
||||
world << sound('sound/effects/explosionfar.ogg')
|
||||
else //station was destroyed
|
||||
if(mode && !override)
|
||||
override = mode.name
|
||||
switch(override)
|
||||
if("nuclear emergency") //Nuke Ops successfully bombed the station
|
||||
flick("intro_nuke", cinematic)
|
||||
sleep(35)
|
||||
flick("station_explode_fade_red", cinematic)
|
||||
world << sound('sound/effects/explosionfar.ogg')
|
||||
cinematic.icon_state = "summary_nukewin"
|
||||
if("AI malfunction") //Malf (screen,explosion,summary)
|
||||
flick("intro_malf", cinematic)
|
||||
sleep(76)
|
||||
flick("station_explode_fade_red", cinematic)
|
||||
world << sound('sound/effects/explosionfar.ogg')
|
||||
cinematic.icon_state = "summary_malf"
|
||||
if("blob") //Station nuked (nuke,explosion,summary)
|
||||
flick("intro_nuke", cinematic)
|
||||
sleep(35)
|
||||
flick("station_explode_fade_red", cinematic)
|
||||
world << sound('sound/effects/explosionfar.ogg')
|
||||
cinematic.icon_state = "summary_selfdes"
|
||||
else //Station nuked (nuke,explosion,summary)
|
||||
flick("intro_nuke", cinematic)
|
||||
sleep(35)
|
||||
flick("station_explode_fade_red", cinematic)
|
||||
world << sound('sound/effects/explosionfar.ogg')
|
||||
cinematic.icon_state = "summary_selfdes"
|
||||
//If its actually the end of the round, wait for it to end.
|
||||
//Otherwise if its a verb it will continue on afterwards.
|
||||
spawn(300)
|
||||
QDEL_NULL(cinematic) //end the cinematic
|
||||
if(temp_buckle)
|
||||
qdel(temp_buckle) //release everybody
|
||||
|
||||
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/create_characters()
|
||||
for(var/mob/new_player/player in GLOB.player_list)
|
||||
if(player.ready && player.mind)
|
||||
if(player.mind.assigned_role == "AI")
|
||||
player.close_spawn_windows()
|
||||
var/mob/living/silicon/ai/ai_character = player.AIize()
|
||||
ai_character.moveToAILandmark()
|
||||
else if(!player.mind.assigned_role)
|
||||
continue
|
||||
else
|
||||
player.create_character()
|
||||
qdel(player)
|
||||
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/collect_minds()
|
||||
for(var/mob/living/player in GLOB.player_list)
|
||||
if(player.mind)
|
||||
SSticker.minds += player.mind
|
||||
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/equip_characters()
|
||||
var/captainless=1
|
||||
for(var/mob/living/carbon/human/player in GLOB.player_list)
|
||||
if(player && player.mind && player.mind.assigned_role)
|
||||
if(player.mind.assigned_role == "Captain")
|
||||
captainless=0
|
||||
if(player.mind.assigned_role != player.mind.special_role)
|
||||
SSjobs.AssignRank(player, player.mind.assigned_role, 0)
|
||||
SSjobs.EquipRank(player, player.mind.assigned_role, 0)
|
||||
EquipCustomItems(player)
|
||||
if(captainless)
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
if(!istype(M,/mob/new_player))
|
||||
to_chat(M, "Captainship not forced on anyone.")
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/send_tip_of_the_round()
|
||||
var/m
|
||||
if(selected_tip)
|
||||
m = selected_tip
|
||||
else
|
||||
var/list/randomtips = file2list("strings/tips.txt")
|
||||
var/list/memetips = file2list("strings/sillytips.txt")
|
||||
if(randomtips.len && prob(95))
|
||||
m = pick(randomtips)
|
||||
else if(memetips.len)
|
||||
m = pick(memetips)
|
||||
|
||||
if(m)
|
||||
to_chat(world, "<span class='purple'><b>Tip of the round: </b>[html_encode(m)]</span>")
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/getfactionbyname(var/name)
|
||||
for(var/datum/faction/F in factions)
|
||||
if(F.name == name)
|
||||
return F
|
||||
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/declare_completion()
|
||||
nologevent = 1 //end of round murder and shenanigans are legal; there's no need to jam up attack logs past this point.
|
||||
//Round statistics report
|
||||
var/datum/station_state/end_state = new /datum/station_state()
|
||||
end_state.count()
|
||||
var/station_integrity = min(round( 100.0 * start_state.score(end_state), 0.1), 100.0)
|
||||
|
||||
to_chat(world, "<BR>[TAB]Shift Duration: <B>[round(ROUND_TIME / 36000)]:[add_zero("[ROUND_TIME / 600 % 60]", 2)]:[ROUND_TIME / 100 % 6][ROUND_TIME / 100 % 10]</B>")
|
||||
to_chat(world, "<BR>[TAB]Station Integrity: <B>[mode.station_was_nuked ? "<font color='red'>Destroyed</font>" : "[station_integrity]%"]</B>")
|
||||
to_chat(world, "<BR>")
|
||||
|
||||
//Silicon laws report
|
||||
for(var/mob/living/silicon/ai/aiPlayer in GLOB.mob_list)
|
||||
if(aiPlayer.stat != 2)
|
||||
to_chat(world, "<b>[aiPlayer.name] (Played by: [aiPlayer.key])'s laws at the end of the game were:</b>")
|
||||
else
|
||||
to_chat(world, "<b>[aiPlayer.name] (Played by: [aiPlayer.key])'s laws when it was deactivated were:</b>")
|
||||
aiPlayer.show_laws(1)
|
||||
|
||||
if(aiPlayer.connected_robots.len)
|
||||
var/robolist = "<b>The AI's loyal minions were:</b> "
|
||||
for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots)
|
||||
robolist += "[robo.name][robo.stat?" (Deactivated) (Played by: [robo.key]), ":" (Played by: [robo.key]), "]"
|
||||
to_chat(world, "[robolist]")
|
||||
|
||||
var/dronecount = 0
|
||||
|
||||
for(var/mob/living/silicon/robot/robo in GLOB.mob_list)
|
||||
|
||||
if(istype(robo,/mob/living/silicon/robot/drone))
|
||||
dronecount++
|
||||
continue
|
||||
|
||||
if(!robo.connected_ai)
|
||||
if(robo.stat != 2)
|
||||
to_chat(world, "<b>[robo.name] (Played by: [robo.key]) survived as an AI-less borg! Its laws were:</b>")
|
||||
else
|
||||
to_chat(world, "<b>[robo.name] (Played by: [robo.key]) was unable to survive the rigors of being a cyborg without an AI. Its laws were:</b>")
|
||||
|
||||
if(robo) //How the hell do we lose robo between here and the world messages directly above this?
|
||||
robo.laws.show_laws(world)
|
||||
|
||||
if(dronecount)
|
||||
to_chat(world, "<b>There [dronecount>1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount>1 ? "drones" : "drone"] this round.")
|
||||
|
||||
if(mode.eventmiscs.len)
|
||||
var/emobtext = ""
|
||||
for(var/datum/mind/eventmind in mode.eventmiscs)
|
||||
emobtext += printeventplayer(eventmind)
|
||||
emobtext += "<br>"
|
||||
emobtext += printobjectives(eventmind)
|
||||
emobtext += "<br>"
|
||||
emobtext += "<br>"
|
||||
to_chat(world, emobtext)
|
||||
|
||||
mode.declare_completion()//To declare normal completion.
|
||||
|
||||
//calls auto_declare_completion_* for all modes
|
||||
for(var/handler in typesof(/datum/game_mode/proc))
|
||||
if(findtext("[handler]","auto_declare_completion_"))
|
||||
call(mode, handler)()
|
||||
|
||||
scoreboard()
|
||||
|
||||
// Declare the completion of the station goals
|
||||
mode.declare_station_goal_completion()
|
||||
|
||||
//Ask the event manager to print round end information
|
||||
SSevents.RoundEnd()
|
||||
|
||||
// Add AntagHUD to everyone, see who was really evil the whole time!
|
||||
for(var/datum/atom_hud/antag/H in huds)
|
||||
for(var/m in GLOB.player_list)
|
||||
var/mob/M = m
|
||||
H.add_hud_to(M)
|
||||
|
||||
return 1
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/HasRoundStarted()
|
||||
return current_state >= GAME_STATE_PLAYING
|
||||
|
||||
@@ -19,7 +19,7 @@ SUBSYSTEM_DEF(vote)
|
||||
/datum/controller/subsystem/vote/fire()
|
||||
if(mode)
|
||||
// No more change mode votes after the game has started.
|
||||
if(mode == "gamemode" && ticker.current_state >= GAME_STATE_SETTING_UP)
|
||||
if(mode == "gamemode" && SSticker.current_state >= GAME_STATE_SETTING_UP)
|
||||
to_chat(world, "<b>Voting aborted due to game start.</b>")
|
||||
reset()
|
||||
return
|
||||
@@ -125,7 +125,7 @@ SUBSYSTEM_DEF(vote)
|
||||
var/text
|
||||
if(winners.len > 0)
|
||||
if(winners.len > 1)
|
||||
if(mode != "gamemode" || ticker.hide_mode == 0) // Here we are making sure we don't announce potential game modes
|
||||
if(mode != "gamemode" || SSticker.hide_mode == 0) // Here we are making sure we don't announce potential game modes
|
||||
text = "<b>Vote Tied Between:</b>\n"
|
||||
for(var/option in winners)
|
||||
text += "\t[option]\n"
|
||||
@@ -134,7 +134,7 @@ SUBSYSTEM_DEF(vote)
|
||||
for(var/key in current_votes)
|
||||
if(choices[current_votes[key]] == .)
|
||||
round_voters += key // Keep track of who voted for the winning round.
|
||||
if(mode == "gamemode" && (. == "extended" || ticker.hide_mode == 0)) // Announce Extended gamemode, but not other gamemodes
|
||||
if(mode == "gamemode" && (. == "extended" || SSticker.hide_mode == 0)) // Announce Extended gamemode, but not other gamemodes
|
||||
text += "<b>Vote Result: [.] ([choices[.]] vote\s)</b>"
|
||||
else
|
||||
if(mode == "custom")
|
||||
@@ -166,7 +166,7 @@ SUBSYSTEM_DEF(vote)
|
||||
if("gamemode")
|
||||
if(master_mode != .)
|
||||
world.save_mode(.)
|
||||
if(ticker && ticker.mode)
|
||||
if(SSticker && SSticker.mode)
|
||||
restart = 1
|
||||
else
|
||||
master_mode = .
|
||||
@@ -208,17 +208,17 @@ SUBSYSTEM_DEF(vote)
|
||||
if("restart")
|
||||
choices.Add("Restart Round","Continue Playing")
|
||||
if("gamemode")
|
||||
if(ticker.current_state >= 2)
|
||||
if(SSticker.current_state >= 2)
|
||||
return 0
|
||||
choices.Add(config.votable_modes)
|
||||
if("crew_transfer")
|
||||
if(check_rights(R_ADMIN|R_MOD))
|
||||
if(ticker.current_state <= 2)
|
||||
if(SSticker.current_state <= 2)
|
||||
return 0
|
||||
question = "End the shift?"
|
||||
choices.Add("Initiate Crew Transfer", "Continue The Round")
|
||||
else
|
||||
if(ticker.current_state <= 2)
|
||||
if(SSticker.current_state <= 2)
|
||||
return 0
|
||||
question = "End the shift?"
|
||||
choices.Add("Initiate Crew Transfer", "Continue The Round")
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
debug_variables(Master)
|
||||
feedback_add_details("admin_verb","Dsmc")
|
||||
if("Ticker")
|
||||
debug_variables(ticker)
|
||||
debug_variables(SSticker)
|
||||
feedback_add_details("admin_verb","DTicker")
|
||||
if("Air")
|
||||
debug_variables(SSair)
|
||||
|
||||
Reference in New Issue
Block a user