mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-08-21 11:07:46 +01:00
Whitespace Standardization [MDB IGNORE] (#15748)
* Update settings * Whitespace changes * Comment out merger hooks in gitattributes Corrupt maps would have to be resolved in repo before hooks could be updated * Revert "Whitespace changes" This reverts commitafbdd1d844. * Whitespace again minus example * Gitignore example changelog * Restore changelog merge setting * Keep older dmi hook attribute until hooks can be updated * update vscode settings too * Renormalize remaining * Revert "Gitignore example changelog" This reverts commitde22ad375d. * Attempt to normalize example.yml (and another file I guess) * Try again
This commit is contained in:
@@ -1,96 +1,96 @@
|
||||
/**
|
||||
* Startup hook.
|
||||
* Called in world.dm when the server starts.
|
||||
*/
|
||||
/hook/startup
|
||||
|
||||
/**
|
||||
* Roundstart hook.
|
||||
* Called in gameticker.dm when a round starts.
|
||||
*/
|
||||
/hook/roundstart
|
||||
|
||||
/**
|
||||
* Roundend hook.
|
||||
* Called in gameticker.dm when a round ends.
|
||||
*/
|
||||
/hook/roundend
|
||||
|
||||
/**
|
||||
* 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/organ/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/alien/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/weapon/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/weapon/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
|
||||
|
||||
/**
|
||||
* Supply Shuttle sold hook.
|
||||
* Called in supplyshuttle.dm when the shuttle contents are sold.
|
||||
* This hook is called _before_ the crates are processed for normal
|
||||
* phoron/metal sale (and before the sell_crate hooks)
|
||||
* Parameters: var/area/area_shuttle
|
||||
*/
|
||||
/hook/sell_shuttle
|
||||
/**
|
||||
* Startup hook.
|
||||
* Called in world.dm when the server starts.
|
||||
*/
|
||||
/hook/startup
|
||||
|
||||
/**
|
||||
* Roundstart hook.
|
||||
* Called in gameticker.dm when a round starts.
|
||||
*/
|
||||
/hook/roundstart
|
||||
|
||||
/**
|
||||
* Roundend hook.
|
||||
* Called in gameticker.dm when a round ends.
|
||||
*/
|
||||
/hook/roundend
|
||||
|
||||
/**
|
||||
* 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/organ/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/alien/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/weapon/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/weapon/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
|
||||
|
||||
/**
|
||||
* Supply Shuttle sold hook.
|
||||
* Called in supplyshuttle.dm when the shuttle contents are sold.
|
||||
* This hook is called _before_ the crates are processed for normal
|
||||
* phoron/metal sale (and before the sell_crate hooks)
|
||||
* Parameters: var/area/area_shuttle
|
||||
*/
|
||||
/hook/sell_shuttle
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
//simplified MC that is designed to fail when procs 'break'. When it fails it's just replaced with a new one.
|
||||
//It ensures master_controller.process() is never doubled up by killing the MC (hence terminating any of its sleeping procs)
|
||||
//WIP, needs lots of work still
|
||||
|
||||
//
|
||||
// TODO - This will be completely replaced by master.dm in time.
|
||||
//
|
||||
|
||||
var/global/datum/controller/game_controller/master_controller //Set in world.New()
|
||||
|
||||
var/global/controller_iteration = 0
|
||||
var/global/last_tick_duration = 0
|
||||
|
||||
var/global/pipe_processing_killed = 0
|
||||
|
||||
/datum/controller/game_controller
|
||||
var/list/shuttle_list // For debugging and VV
|
||||
|
||||
/datum/controller/game_controller/New()
|
||||
//There can be only one master_controller. Out with the old and in with the new.
|
||||
if(master_controller != src)
|
||||
log_debug("Rebuilding Master Controller")
|
||||
if(istype(master_controller))
|
||||
qdel(master_controller)
|
||||
master_controller = src
|
||||
|
||||
if(!job_master)
|
||||
job_master = new /datum/controller/occupations()
|
||||
job_master.SetupOccupations()
|
||||
job_master.LoadJobs("config/jobs.txt")
|
||||
admin_notice("<span class='danger'>Job setup complete</span>", R_DEBUG)
|
||||
|
||||
if(!syndicate_code_phrase) syndicate_code_phrase = generate_code_phrase()
|
||||
if(!syndicate_code_response) syndicate_code_response = generate_code_phrase()
|
||||
|
||||
/datum/controller/game_controller/proc/setup()
|
||||
|
||||
setup_objects()
|
||||
// setupgenetics() Moved to SSatoms
|
||||
// SetupXenoarch() - Moved to SSxenoarch
|
||||
|
||||
transfer_controller = new
|
||||
admin_notice("<span class='danger'>Initializations complete.</span>", R_DEBUG)
|
||||
|
||||
// #if UNIT_TEST
|
||||
// #define CHECK_SLEEP_MASTER // For unit tests we don't care about a smooth lobby screen experience. We care about speed.
|
||||
// #else
|
||||
// #define CHECK_SLEEP_MASTER if(++initialized_objects > 500) { initialized_objects=0;sleep(world.tick_lag); }
|
||||
// #endif
|
||||
|
||||
/datum/controller/game_controller/proc/setup_objects()
|
||||
// Set up antagonists.
|
||||
populate_antag_type_list()
|
||||
|
||||
//Set up spawn points.
|
||||
//simplified MC that is designed to fail when procs 'break'. When it fails it's just replaced with a new one.
|
||||
//It ensures master_controller.process() is never doubled up by killing the MC (hence terminating any of its sleeping procs)
|
||||
//WIP, needs lots of work still
|
||||
|
||||
//
|
||||
// TODO - This will be completely replaced by master.dm in time.
|
||||
//
|
||||
|
||||
var/global/datum/controller/game_controller/master_controller //Set in world.New()
|
||||
|
||||
var/global/controller_iteration = 0
|
||||
var/global/last_tick_duration = 0
|
||||
|
||||
var/global/pipe_processing_killed = 0
|
||||
|
||||
/datum/controller/game_controller
|
||||
var/list/shuttle_list // For debugging and VV
|
||||
|
||||
/datum/controller/game_controller/New()
|
||||
//There can be only one master_controller. Out with the old and in with the new.
|
||||
if(master_controller != src)
|
||||
log_debug("Rebuilding Master Controller")
|
||||
if(istype(master_controller))
|
||||
qdel(master_controller)
|
||||
master_controller = src
|
||||
|
||||
if(!job_master)
|
||||
job_master = new /datum/controller/occupations()
|
||||
job_master.SetupOccupations()
|
||||
job_master.LoadJobs("config/jobs.txt")
|
||||
admin_notice("<span class='danger'>Job setup complete</span>", R_DEBUG)
|
||||
|
||||
if(!syndicate_code_phrase) syndicate_code_phrase = generate_code_phrase()
|
||||
if(!syndicate_code_response) syndicate_code_response = generate_code_phrase()
|
||||
|
||||
/datum/controller/game_controller/proc/setup()
|
||||
|
||||
setup_objects()
|
||||
// setupgenetics() Moved to SSatoms
|
||||
// SetupXenoarch() - Moved to SSxenoarch
|
||||
|
||||
transfer_controller = new
|
||||
admin_notice("<span class='danger'>Initializations complete.</span>", R_DEBUG)
|
||||
|
||||
// #if UNIT_TEST
|
||||
// #define CHECK_SLEEP_MASTER // For unit tests we don't care about a smooth lobby screen experience. We care about speed.
|
||||
// #else
|
||||
// #define CHECK_SLEEP_MASTER if(++initialized_objects > 500) { initialized_objects=0;sleep(world.tick_lag); }
|
||||
// #endif
|
||||
|
||||
/datum/controller/game_controller/proc/setup_objects()
|
||||
// Set up antagonists.
|
||||
populate_antag_type_list()
|
||||
|
||||
//Set up spawn points.
|
||||
populate_spawn_points()
|
||||
@@ -1,31 +1,31 @@
|
||||
#define OBSERVER_EVENT_DESTROY "OnDestroy"
|
||||
|
||||
/atom
|
||||
var/list/observer_events
|
||||
|
||||
/atom/Destroy()
|
||||
var/list/destroy_listeners = get_listener_list_from_event(OBSERVER_EVENT_DESTROY)
|
||||
if(destroy_listeners)
|
||||
for(var/destroy_listener in destroy_listeners)
|
||||
call(destroy_listener, destroy_listeners[destroy_listener])(src)
|
||||
|
||||
for(var/list/listeners in observer_events)
|
||||
listeners.Cut()
|
||||
|
||||
return ..()
|
||||
|
||||
/atom/proc/register(var/event, var/procOwner, var/proc_call)
|
||||
var/list/listeners = get_listener_list_from_event(event)
|
||||
listeners[procOwner] = proc_call
|
||||
|
||||
/atom/proc/unregister(var/event, var/procOwner)
|
||||
var/list/listeners = get_listener_list_from_event(event)
|
||||
listeners -= procOwner
|
||||
|
||||
/atom/proc/get_listener_list_from_event(var/observer_event)
|
||||
if(!observer_events) observer_events = list()
|
||||
var/list/listeners = observer_events[observer_event]
|
||||
if(!listeners)
|
||||
listeners = list()
|
||||
observer_events[observer_event] = listeners
|
||||
return listeners
|
||||
#define OBSERVER_EVENT_DESTROY "OnDestroy"
|
||||
|
||||
/atom
|
||||
var/list/observer_events
|
||||
|
||||
/atom/Destroy()
|
||||
var/list/destroy_listeners = get_listener_list_from_event(OBSERVER_EVENT_DESTROY)
|
||||
if(destroy_listeners)
|
||||
for(var/destroy_listener in destroy_listeners)
|
||||
call(destroy_listener, destroy_listeners[destroy_listener])(src)
|
||||
|
||||
for(var/list/listeners in observer_events)
|
||||
listeners.Cut()
|
||||
|
||||
return ..()
|
||||
|
||||
/atom/proc/register(var/event, var/procOwner, var/proc_call)
|
||||
var/list/listeners = get_listener_list_from_event(event)
|
||||
listeners[procOwner] = proc_call
|
||||
|
||||
/atom/proc/unregister(var/event, var/procOwner)
|
||||
var/list/listeners = get_listener_list_from_event(event)
|
||||
listeners -= procOwner
|
||||
|
||||
/atom/proc/get_listener_list_from_event(var/observer_event)
|
||||
if(!observer_events) observer_events = list()
|
||||
var/list/listeners = observer_events[observer_event]
|
||||
if(!listeners)
|
||||
listeners = list()
|
||||
observer_events[observer_event] = listeners
|
||||
return listeners
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
|
||||
// We manually initialize the alarm handlers instead of looping over all existing types
|
||||
// to make it possible to write: camera_alarm.triggerAlarm() rather than SSalarm.managers[datum/alarm_handler/camera].triggerAlarm() or a variant thereof.
|
||||
/var/global/datum/alarm_handler/atmosphere/atmosphere_alarm = new()
|
||||
/var/global/datum/alarm_handler/camera/camera_alarm = new()
|
||||
/var/global/datum/alarm_handler/fire/fire_alarm = new()
|
||||
/var/global/datum/alarm_handler/motion/motion_alarm = new()
|
||||
/var/global/datum/alarm_handler/power/power_alarm = new()
|
||||
|
||||
SUBSYSTEM_DEF(alarm)
|
||||
name = "Alarm"
|
||||
wait = 2 SECONDS
|
||||
priority = FIRE_PRIORITY_ALARM
|
||||
init_order = INIT_ORDER_ALARM
|
||||
var/list/datum/alarm/all_handlers
|
||||
var/tmp/list/currentrun = null
|
||||
var/static/list/active_alarm_cache = list()
|
||||
|
||||
/datum/controller/subsystem/alarm/Initialize()
|
||||
all_handlers = list(atmosphere_alarm, camera_alarm, fire_alarm, motion_alarm, power_alarm)
|
||||
. = ..()
|
||||
|
||||
/datum/controller/subsystem/alarm/fire(resumed = FALSE)
|
||||
if(!resumed)
|
||||
src.currentrun = all_handlers.Copy()
|
||||
active_alarm_cache.Cut()
|
||||
|
||||
var/list/currentrun = src.currentrun // Cache for sanic speed
|
||||
while (currentrun.len)
|
||||
var/datum/alarm_handler/AH = currentrun[currentrun.len]
|
||||
currentrun.len--
|
||||
AH.process()
|
||||
active_alarm_cache += AH.alarms
|
||||
|
||||
if (MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
/datum/controller/subsystem/alarm/proc/active_alarms()
|
||||
return active_alarm_cache.Copy()
|
||||
|
||||
/datum/controller/subsystem/alarm/proc/number_of_active_alarms()
|
||||
return active_alarm_cache.len
|
||||
|
||||
/datum/controller/subsystem/alarm/stat_entry()
|
||||
..("[number_of_active_alarms()] alarm\s")
|
||||
|
||||
// We manually initialize the alarm handlers instead of looping over all existing types
|
||||
// to make it possible to write: camera_alarm.triggerAlarm() rather than SSalarm.managers[datum/alarm_handler/camera].triggerAlarm() or a variant thereof.
|
||||
/var/global/datum/alarm_handler/atmosphere/atmosphere_alarm = new()
|
||||
/var/global/datum/alarm_handler/camera/camera_alarm = new()
|
||||
/var/global/datum/alarm_handler/fire/fire_alarm = new()
|
||||
/var/global/datum/alarm_handler/motion/motion_alarm = new()
|
||||
/var/global/datum/alarm_handler/power/power_alarm = new()
|
||||
|
||||
SUBSYSTEM_DEF(alarm)
|
||||
name = "Alarm"
|
||||
wait = 2 SECONDS
|
||||
priority = FIRE_PRIORITY_ALARM
|
||||
init_order = INIT_ORDER_ALARM
|
||||
var/list/datum/alarm/all_handlers
|
||||
var/tmp/list/currentrun = null
|
||||
var/static/list/active_alarm_cache = list()
|
||||
|
||||
/datum/controller/subsystem/alarm/Initialize()
|
||||
all_handlers = list(atmosphere_alarm, camera_alarm, fire_alarm, motion_alarm, power_alarm)
|
||||
. = ..()
|
||||
|
||||
/datum/controller/subsystem/alarm/fire(resumed = FALSE)
|
||||
if(!resumed)
|
||||
src.currentrun = all_handlers.Copy()
|
||||
active_alarm_cache.Cut()
|
||||
|
||||
var/list/currentrun = src.currentrun // Cache for sanic speed
|
||||
while (currentrun.len)
|
||||
var/datum/alarm_handler/AH = currentrun[currentrun.len]
|
||||
currentrun.len--
|
||||
AH.process()
|
||||
active_alarm_cache += AH.alarms
|
||||
|
||||
if (MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
/datum/controller/subsystem/alarm/proc/active_alarms()
|
||||
return active_alarm_cache.Copy()
|
||||
|
||||
/datum/controller/subsystem/alarm/proc/number_of_active_alarms()
|
||||
return active_alarm_cache.len
|
||||
|
||||
/datum/controller/subsystem/alarm/stat_entry()
|
||||
..("[number_of_active_alarms()] alarm\s")
|
||||
|
||||
@@ -1,94 +1,94 @@
|
||||
//
|
||||
// This is for custom circuits, mostly the initialization of global properties about them.
|
||||
// Might make this also process them in the future if its better to do that than using the obj ticker.
|
||||
//
|
||||
SUBSYSTEM_DEF(circuit)
|
||||
name = "Circuit"
|
||||
init_order = INIT_ORDER_CIRCUIT
|
||||
flags = SS_NO_FIRE
|
||||
var/list/all_components = list() // Associative list of [component_name]:[component_path] pairs
|
||||
var/list/cached_components = list() // Associative list of [component_path]:[component] pairs
|
||||
var/list/all_assemblies = list() // Associative list of [assembly_name]:[assembly_path] pairs
|
||||
var/list/cached_assemblies = list() // Associative list of [assembly_path]:[assembly] pairs
|
||||
var/list/all_circuits = list() // Associative list of [circuit_name]:[circuit_path] pairs
|
||||
var/list/circuit_fabricator_recipe_list = list() // Associative list of [category_name]:[list_of_circuit_paths] pairs
|
||||
// var/cost_multiplier = MINERAL_MATERIAL_AMOUNT / 10 // Each circuit cost unit is 200cm3
|
||||
|
||||
/datum/controller/subsystem/circuit/Recover()
|
||||
flags |= SS_NO_INIT // Make extra sure we don't initialize twice.
|
||||
|
||||
/datum/controller/subsystem/circuit/Initialize(timeofday)
|
||||
circuits_init()
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/circuit/proc/circuits_init()
|
||||
//Cached lists for free performance
|
||||
for(var/obj/item/integrated_circuit/IC as anything in typesof(/obj/item/integrated_circuit))
|
||||
var/path = IC
|
||||
all_components[initial(IC.name)] = path // Populating the component lists
|
||||
cached_components[path] = new path
|
||||
|
||||
if(!(initial(IC.spawn_flags) & (IC_SPAWN_DEFAULT | IC_SPAWN_RESEARCH)))
|
||||
continue
|
||||
|
||||
var/category = initial(IC.category_text)
|
||||
if(!circuit_fabricator_recipe_list[category])
|
||||
circuit_fabricator_recipe_list[category] = list()
|
||||
var/list/category_list = circuit_fabricator_recipe_list[category]
|
||||
category_list += IC // Populating the fabricator categories
|
||||
|
||||
for(var/obj/item/device/electronic_assembly/A as anything in typesof(/obj/item/device/electronic_assembly))
|
||||
var/path = A
|
||||
all_assemblies[initial(A.name)] = path
|
||||
cached_assemblies[path] = new path
|
||||
|
||||
|
||||
circuit_fabricator_recipe_list["Assemblies"] = list(
|
||||
/obj/item/device/electronic_assembly/default,
|
||||
/obj/item/device/electronic_assembly/calc,
|
||||
/obj/item/device/electronic_assembly/clam,
|
||||
/obj/item/device/electronic_assembly/simple,
|
||||
/obj/item/device/electronic_assembly/hook,
|
||||
/obj/item/device/electronic_assembly/pda,
|
||||
/obj/item/device/electronic_assembly/tiny/default,
|
||||
/obj/item/device/electronic_assembly/tiny/cylinder,
|
||||
/obj/item/device/electronic_assembly/tiny/scanner,
|
||||
/obj/item/device/electronic_assembly/tiny/hook,
|
||||
/obj/item/device/electronic_assembly/tiny/box,
|
||||
/obj/item/device/electronic_assembly/medium/default,
|
||||
/obj/item/device/electronic_assembly/medium/box,
|
||||
/obj/item/device/electronic_assembly/medium/clam,
|
||||
/obj/item/device/electronic_assembly/medium/medical,
|
||||
/obj/item/device/electronic_assembly/medium/gun,
|
||||
/obj/item/device/electronic_assembly/medium/radio,
|
||||
/obj/item/device/electronic_assembly/large/default,
|
||||
/obj/item/device/electronic_assembly/large/scope,
|
||||
/obj/item/device/electronic_assembly/large/terminal,
|
||||
/obj/item/device/electronic_assembly/large/arm,
|
||||
/obj/item/device/electronic_assembly/large/tall,
|
||||
/obj/item/device/electronic_assembly/large/industrial,
|
||||
/obj/item/device/electronic_assembly/drone/default,
|
||||
/obj/item/device/electronic_assembly/drone/arms,
|
||||
/obj/item/device/electronic_assembly/drone/secbot,
|
||||
/obj/item/device/electronic_assembly/drone/medbot,
|
||||
/obj/item/device/electronic_assembly/drone/genbot,
|
||||
/obj/item/device/electronic_assembly/drone/android,
|
||||
/obj/item/device/electronic_assembly/wallmount/tiny,
|
||||
/obj/item/device/electronic_assembly/wallmount/light,
|
||||
/obj/item/device/electronic_assembly/wallmount,
|
||||
/obj/item/device/electronic_assembly/wallmount/heavy,
|
||||
/obj/item/weapon/implant/integrated_circuit,
|
||||
/obj/item/clothing/under/circuitry,
|
||||
/obj/item/clothing/gloves/circuitry,
|
||||
/obj/item/clothing/glasses/circuitry,
|
||||
/obj/item/clothing/shoes/circuitry,
|
||||
/obj/item/clothing/head/circuitry,
|
||||
/obj/item/clothing/ears/circuitry,
|
||||
/obj/item/clothing/suit/circuitry
|
||||
)
|
||||
|
||||
circuit_fabricator_recipe_list["Tools"] = list(
|
||||
/obj/item/device/integrated_electronics/wirer,
|
||||
/obj/item/device/integrated_electronics/debugger,
|
||||
/obj/item/device/integrated_electronics/detailer
|
||||
)
|
||||
//
|
||||
// This is for custom circuits, mostly the initialization of global properties about them.
|
||||
// Might make this also process them in the future if its better to do that than using the obj ticker.
|
||||
//
|
||||
SUBSYSTEM_DEF(circuit)
|
||||
name = "Circuit"
|
||||
init_order = INIT_ORDER_CIRCUIT
|
||||
flags = SS_NO_FIRE
|
||||
var/list/all_components = list() // Associative list of [component_name]:[component_path] pairs
|
||||
var/list/cached_components = list() // Associative list of [component_path]:[component] pairs
|
||||
var/list/all_assemblies = list() // Associative list of [assembly_name]:[assembly_path] pairs
|
||||
var/list/cached_assemblies = list() // Associative list of [assembly_path]:[assembly] pairs
|
||||
var/list/all_circuits = list() // Associative list of [circuit_name]:[circuit_path] pairs
|
||||
var/list/circuit_fabricator_recipe_list = list() // Associative list of [category_name]:[list_of_circuit_paths] pairs
|
||||
// var/cost_multiplier = MINERAL_MATERIAL_AMOUNT / 10 // Each circuit cost unit is 200cm3
|
||||
|
||||
/datum/controller/subsystem/circuit/Recover()
|
||||
flags |= SS_NO_INIT // Make extra sure we don't initialize twice.
|
||||
|
||||
/datum/controller/subsystem/circuit/Initialize(timeofday)
|
||||
circuits_init()
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/circuit/proc/circuits_init()
|
||||
//Cached lists for free performance
|
||||
for(var/obj/item/integrated_circuit/IC as anything in typesof(/obj/item/integrated_circuit))
|
||||
var/path = IC
|
||||
all_components[initial(IC.name)] = path // Populating the component lists
|
||||
cached_components[path] = new path
|
||||
|
||||
if(!(initial(IC.spawn_flags) & (IC_SPAWN_DEFAULT | IC_SPAWN_RESEARCH)))
|
||||
continue
|
||||
|
||||
var/category = initial(IC.category_text)
|
||||
if(!circuit_fabricator_recipe_list[category])
|
||||
circuit_fabricator_recipe_list[category] = list()
|
||||
var/list/category_list = circuit_fabricator_recipe_list[category]
|
||||
category_list += IC // Populating the fabricator categories
|
||||
|
||||
for(var/obj/item/device/electronic_assembly/A as anything in typesof(/obj/item/device/electronic_assembly))
|
||||
var/path = A
|
||||
all_assemblies[initial(A.name)] = path
|
||||
cached_assemblies[path] = new path
|
||||
|
||||
|
||||
circuit_fabricator_recipe_list["Assemblies"] = list(
|
||||
/obj/item/device/electronic_assembly/default,
|
||||
/obj/item/device/electronic_assembly/calc,
|
||||
/obj/item/device/electronic_assembly/clam,
|
||||
/obj/item/device/electronic_assembly/simple,
|
||||
/obj/item/device/electronic_assembly/hook,
|
||||
/obj/item/device/electronic_assembly/pda,
|
||||
/obj/item/device/electronic_assembly/tiny/default,
|
||||
/obj/item/device/electronic_assembly/tiny/cylinder,
|
||||
/obj/item/device/electronic_assembly/tiny/scanner,
|
||||
/obj/item/device/electronic_assembly/tiny/hook,
|
||||
/obj/item/device/electronic_assembly/tiny/box,
|
||||
/obj/item/device/electronic_assembly/medium/default,
|
||||
/obj/item/device/electronic_assembly/medium/box,
|
||||
/obj/item/device/electronic_assembly/medium/clam,
|
||||
/obj/item/device/electronic_assembly/medium/medical,
|
||||
/obj/item/device/electronic_assembly/medium/gun,
|
||||
/obj/item/device/electronic_assembly/medium/radio,
|
||||
/obj/item/device/electronic_assembly/large/default,
|
||||
/obj/item/device/electronic_assembly/large/scope,
|
||||
/obj/item/device/electronic_assembly/large/terminal,
|
||||
/obj/item/device/electronic_assembly/large/arm,
|
||||
/obj/item/device/electronic_assembly/large/tall,
|
||||
/obj/item/device/electronic_assembly/large/industrial,
|
||||
/obj/item/device/electronic_assembly/drone/default,
|
||||
/obj/item/device/electronic_assembly/drone/arms,
|
||||
/obj/item/device/electronic_assembly/drone/secbot,
|
||||
/obj/item/device/electronic_assembly/drone/medbot,
|
||||
/obj/item/device/electronic_assembly/drone/genbot,
|
||||
/obj/item/device/electronic_assembly/drone/android,
|
||||
/obj/item/device/electronic_assembly/wallmount/tiny,
|
||||
/obj/item/device/electronic_assembly/wallmount/light,
|
||||
/obj/item/device/electronic_assembly/wallmount,
|
||||
/obj/item/device/electronic_assembly/wallmount/heavy,
|
||||
/obj/item/weapon/implant/integrated_circuit,
|
||||
/obj/item/clothing/under/circuitry,
|
||||
/obj/item/clothing/gloves/circuitry,
|
||||
/obj/item/clothing/glasses/circuitry,
|
||||
/obj/item/clothing/shoes/circuitry,
|
||||
/obj/item/clothing/head/circuitry,
|
||||
/obj/item/clothing/ears/circuitry,
|
||||
/obj/item/clothing/suit/circuitry
|
||||
)
|
||||
|
||||
circuit_fabricator_recipe_list["Tools"] = list(
|
||||
/obj/item/device/integrated_electronics/wirer,
|
||||
/obj/item/device/integrated_electronics/debugger,
|
||||
/obj/item/device/integrated_electronics/detailer
|
||||
)
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
// This is a simple ticker for the new event system.
|
||||
// The logic that determines what events get chosen is held inside a seperate subsystem.
|
||||
|
||||
SUBSYSTEM_DEF(event_ticker)
|
||||
name = "Events (Ticker)"
|
||||
wait = 2 SECONDS
|
||||
runlevels = RUNLEVEL_GAME
|
||||
|
||||
// List of `/datum/event2/event`s that are currently active, and receiving process() ticks.
|
||||
var/list/active_events = list()
|
||||
|
||||
// List of `/datum/event2/event`s that finished, and are here for showing at roundend, if that's desired.
|
||||
var/list/finished_events = list()
|
||||
|
||||
// Process active events.
|
||||
/datum/controller/subsystem/event_ticker/fire(resumed)
|
||||
for(var/datum/event2/event/event as anything in active_events)
|
||||
event.process()
|
||||
if(event.finished)
|
||||
event_finished(event)
|
||||
|
||||
// Starts an event, independent of the GM system.
|
||||
// This means it will always run, and won't affect the GM system in any way, e.g. not putting the event off limits after one use.
|
||||
/datum/controller/subsystem/event_ticker/proc/start_event(event_type)
|
||||
var/datum/event2/event/E = new event_type()
|
||||
E.execute()
|
||||
event_started(E)
|
||||
|
||||
/datum/controller/subsystem/event_ticker/proc/event_started(datum/event2/event/E)
|
||||
log_debug("Event [E.type] is now being ran.")
|
||||
active_events += E
|
||||
|
||||
/datum/controller/subsystem/event_ticker/proc/event_finished(datum/event2/event/E)
|
||||
log_debug("Event [E.type] has finished.")
|
||||
active_events -= E
|
||||
// This is a simple ticker for the new event system.
|
||||
// The logic that determines what events get chosen is held inside a seperate subsystem.
|
||||
|
||||
SUBSYSTEM_DEF(event_ticker)
|
||||
name = "Events (Ticker)"
|
||||
wait = 2 SECONDS
|
||||
runlevels = RUNLEVEL_GAME
|
||||
|
||||
// List of `/datum/event2/event`s that are currently active, and receiving process() ticks.
|
||||
var/list/active_events = list()
|
||||
|
||||
// List of `/datum/event2/event`s that finished, and are here for showing at roundend, if that's desired.
|
||||
var/list/finished_events = list()
|
||||
|
||||
// Process active events.
|
||||
/datum/controller/subsystem/event_ticker/fire(resumed)
|
||||
for(var/datum/event2/event/event as anything in active_events)
|
||||
event.process()
|
||||
if(event.finished)
|
||||
event_finished(event)
|
||||
|
||||
// Starts an event, independent of the GM system.
|
||||
// This means it will always run, and won't affect the GM system in any way, e.g. not putting the event off limits after one use.
|
||||
/datum/controller/subsystem/event_ticker/proc/start_event(event_type)
|
||||
var/datum/event2/event/E = new event_type()
|
||||
E.execute()
|
||||
event_started(E)
|
||||
|
||||
/datum/controller/subsystem/event_ticker/proc/event_started(datum/event2/event/E)
|
||||
log_debug("Event [E.type] is now being ran.")
|
||||
active_events += E
|
||||
|
||||
/datum/controller/subsystem/event_ticker/proc/event_finished(datum/event2/event/E)
|
||||
log_debug("Event [E.type] has finished.")
|
||||
active_events -= E
|
||||
finished_events += E
|
||||
@@ -1,364 +1,364 @@
|
||||
// This is a sort of successor to the various event systems created over the years. It is designed to be just a tad smarter than the
|
||||
// previous ones, checking various things like player count, department size and composition, individual player activity,
|
||||
// individual player (IC) skill, and such, in order to try to choose the best events to take in order to add spice or variety to
|
||||
// the round.
|
||||
|
||||
// This subsystem holds the logic that chooses events. Actual event processing is handled in a seperate subsystem.
|
||||
SUBSYSTEM_DEF(game_master)
|
||||
name = "Events (Game Master)"
|
||||
wait = 1 MINUTE
|
||||
runlevels = RUNLEVEL_GAME
|
||||
|
||||
// The GM object is what actually chooses events.
|
||||
// It's held in a seperate object for better encapsulation, and allows for different 'flavors' of GMs to be made, that choose events differently.
|
||||
var/datum/game_master/GM = null
|
||||
var/game_master_type = /datum/game_master/default
|
||||
|
||||
var/list/available_events = list() // A list of meta event objects.
|
||||
|
||||
var/danger = 0 // The GM's best guess at how chaotic the round is. High danger makes it hold back.
|
||||
var/staleness = -20 // Determines liklihood of the GM doing something, increases over time.
|
||||
|
||||
var/next_event = 0 // Minimum amount of time of nothingness until the GM can pick something again.
|
||||
|
||||
var/debug_messages = FALSE // If true, debug information is written to `log_debug()`.
|
||||
|
||||
/datum/controller/subsystem/game_master/Initialize()
|
||||
var/list/subtypes = subtypesof(/datum/event2/meta)
|
||||
for(var/T in subtypes)
|
||||
var/datum/event2/meta/M = new T()
|
||||
if(!M.name)
|
||||
continue
|
||||
available_events += M
|
||||
|
||||
GM = new game_master_type()
|
||||
|
||||
if(config && !config.enable_game_master)
|
||||
can_fire = FALSE
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/game_master/fire(resumed)
|
||||
adjust_staleness(1)
|
||||
adjust_danger(-1)
|
||||
|
||||
var/global_afk = metric.assess_all_living_mobs()
|
||||
global_afk = abs(global_afk - 100)
|
||||
global_afk = round(global_afk / 100, 0.1)
|
||||
adjust_staleness(global_afk) // Staleness increases faster if more people are less active.
|
||||
|
||||
if(GM.ignore_time_restrictions || next_event < world.time)
|
||||
if(prob(staleness) && pre_event_checks())
|
||||
do_event_decision()
|
||||
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/do_event_decision()
|
||||
log_game_master("Going to choose an event.")
|
||||
var/datum/event2/meta/event_picked = GM.choose_event()
|
||||
if(event_picked)
|
||||
run_event(event_picked)
|
||||
next_event = world.time + rand(GM.decision_cooldown_lower_bound, GM.decision_cooldown_upper_bound)
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/debug_gm()
|
||||
can_fire = TRUE
|
||||
staleness = 100
|
||||
debug_messages = TRUE
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/run_event(datum/event2/meta/chosen_event)
|
||||
var/datum/event2/event/E = chosen_event.make_event()
|
||||
|
||||
chosen_event.times_ran++
|
||||
|
||||
if(!chosen_event.reusable)
|
||||
// Disable this event, so it only gets picked once.
|
||||
chosen_event.enabled = FALSE
|
||||
if(chosen_event.event_class)
|
||||
// Disable similar events, too.
|
||||
for(var/datum/event2/meta/meta as anything in available_events)
|
||||
if(meta.event_class == chosen_event.event_class)
|
||||
meta.enabled = FALSE
|
||||
|
||||
SSevent_ticker.event_started(E)
|
||||
adjust_danger(chosen_event.chaos)
|
||||
adjust_staleness(-(10 + chosen_event.chaos)) // More chaotic events reduce staleness more, e.g. a 25 chaos event will reduce it by 35.
|
||||
|
||||
|
||||
// Tell the game master that something dangerous happened, e.g. someone dying, station explosions.
|
||||
/datum/controller/subsystem/game_master/proc/adjust_danger(amount)
|
||||
amount *= GM.danger_modifier
|
||||
danger = round(between(0, danger + amount, 1000), 0.1)
|
||||
|
||||
// Tell the game master that things are getting boring if positive, or something interesting if negative..
|
||||
/datum/controller/subsystem/game_master/proc/adjust_staleness(amount)
|
||||
amount *= GM.staleness_modifier
|
||||
staleness = round( between(-20, staleness + amount, 100), 0.1)
|
||||
|
||||
// These are ran before committing to an event.
|
||||
// Returns TRUE if the system is allowed to procede, otherwise returns FALSE.
|
||||
/datum/controller/subsystem/game_master/proc/pre_event_checks(quiet = FALSE)
|
||||
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
|
||||
if(!quiet)
|
||||
log_game_master("Unable to start event: Ticker is nonexistent, or the game is not ongoing.")
|
||||
return FALSE
|
||||
if(GM.ignore_time_restrictions)
|
||||
return TRUE
|
||||
if(next_event > world.time) // Sanity.
|
||||
if(!quiet)
|
||||
log_game_master("Unable to start event: Time until next event is approximately [round((next_event - world.time) / (1 MINUTE))] minute(s)")
|
||||
return FALSE
|
||||
|
||||
// Last minute antagging is bad for humans to do, so the GM will respect the start and end of the round.
|
||||
var/mills = round_duration_in_ds
|
||||
var/mins = round((mills % 36000) / 600)
|
||||
var/hours = round(mills / 36000)
|
||||
|
||||
// if(hours < 1 && mins <= 20) // Don't do anything for the first twenty minutes of the round.
|
||||
// if(!quiet)
|
||||
// log_debug("Game Master unable to start event: It is too early.")
|
||||
// return FALSE
|
||||
if(hours >= 2 && mins >= 40) // Don't do anything in the last twenty minutes of the round, as well.
|
||||
if(!quiet)
|
||||
log_game_master("Unable to start event: It is too late.")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/choose_game_master(mob/user)
|
||||
var/list/subtypes = subtypesof(/datum/game_master)
|
||||
var/new_gm_path = tgui_input_list(user, "What kind of Game Master do you want?", "New Game Master", subtypes)
|
||||
if(new_gm_path)
|
||||
log_and_message_admins("has swapped the current GM ([GM.type]) for a new GM ([new_gm_path]).")
|
||||
GM = new new_gm_path(src)
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/log_game_master(message)
|
||||
if(debug_messages)
|
||||
log_debug("GAME MASTER: [message]")
|
||||
|
||||
|
||||
// This object makes the actual decisions.
|
||||
/datum/game_master
|
||||
// Multiplier for how much 'danger' is accumulated. Higer generally makes it possible for more dangerous events to be picked.
|
||||
var/danger_modifier = 1.0
|
||||
|
||||
// Ditto. Higher numbers generally result in more events occuring in a round.
|
||||
var/staleness_modifier = 1.0
|
||||
|
||||
var/decision_cooldown_lower_bound = 5 MINUTES // Lower bound for how long to wait until -the potential- for another event being decided.
|
||||
var/decision_cooldown_upper_bound = 20 MINUTES // Same, but upper bound.
|
||||
|
||||
var/ignore_time_restrictions = FALSE // Useful for debugging without needing to wait 20 minutes each time.
|
||||
var/ignore_round_chaos = FALSE // If true, the system will happily choose back to back intense events like meteors and blobs, Dwarf Fortress style.
|
||||
|
||||
/client/proc/show_gm_status()
|
||||
set category = "Debug"
|
||||
set name = "Show GM Status"
|
||||
set desc = "Shows you what the GM is thinking. If only that existed in real life..."
|
||||
|
||||
if(check_rights(R_ADMIN|R_EVENT|R_DEBUG))
|
||||
SSgame_master.interact(usr)
|
||||
else
|
||||
to_chat(usr, span("warning", "You do not have sufficient rights to view the GM panel, sorry."))
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/interact(var/client/user)
|
||||
if(!user)
|
||||
return
|
||||
|
||||
// Using lists for string tree conservation.
|
||||
var/list/dat = list("<html><head><title>Automated Game Master Event System</title></head><body>")
|
||||
|
||||
// Makes the system turn on or off.
|
||||
dat += href(src, list("toggle" = 1), "\[Toggle GM\]")
|
||||
dat += " | "
|
||||
|
||||
// Makes the system not care about staleness or being near round-end.
|
||||
dat += href(src, list("toggle_time_restrictions" = 1), "\[Toggle Time Restrictions\]")
|
||||
dat += " | "
|
||||
|
||||
// Makes the system not care about how chaotic the round might be.
|
||||
dat += href(src, list("toggle_chaos_throttle" = 1), "\[Toggle Chaos Throttling\]")
|
||||
dat += " | "
|
||||
|
||||
// Makes the system immediately choose an event, while still bound to factors like danger, weights, and department staffing.
|
||||
dat += href(src, list("force_choose_event" = 1), "\[Force Event Decision\]")
|
||||
dat += "<br>"
|
||||
|
||||
// Swaps out the current GM for a new one with different ideas on what a good event might be.
|
||||
dat += href(src, list("change_gm" = 1), "\[Change GM\]")
|
||||
dat += "<br>"
|
||||
|
||||
dat += "Current GM Type: [GM.type]<br>"
|
||||
dat += "State: [can_fire ? "Active": "Inactive"]<br>"
|
||||
dat += "Status: [pre_event_checks(TRUE) ? "Ready" : "Suppressed"]<br><br>"
|
||||
|
||||
dat += "Staleness: [staleness] "
|
||||
dat += href(src, list("set_staleness" = 1), "\[Set\]")
|
||||
dat += "<br>"
|
||||
dat += "<i>Staleness is an estimate of how boring the round might be, and if an event should be done. It is increased passively over time, \
|
||||
and increases faster if people are AFK. It deceases when events and certain 'interesting' things happen in the round.</i><br>"
|
||||
|
||||
dat += "Danger: [danger] "
|
||||
dat += href(src, list("set_danger" = 1), "\[Set\]")
|
||||
dat += "<br>"
|
||||
dat += "<i>Danger is an estimate of how chaotic the round has been so far. It is decreased passively over time, and is increased by having \
|
||||
certain chaotic events be selected, or chaotic things happen in the round. A sufficently high amount of danger will make the system \
|
||||
avoid using destructive events, to avoid pushing the station over the edge.</i><br>"
|
||||
|
||||
dat += "<h2>Player Activity:</h2>"
|
||||
|
||||
dat += "<table border='1' style='width:100%'>"
|
||||
dat += "<tr>"
|
||||
dat += "<th>Category</th>"
|
||||
dat += "<th>Activity Percentage</th>"
|
||||
dat += "</tr>"
|
||||
|
||||
dat += "<tr>"
|
||||
dat += "<td>All Living Mobs</td>"
|
||||
dat += "<td>[metric.assess_all_living_mobs()]%</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
dat += "<tr>"
|
||||
dat += "<td>All Ghosts</td>"
|
||||
dat += "<td>[metric.assess_all_dead_mobs()]%</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
dat += "<tr>"
|
||||
dat += "<th colspan='2'>Departments</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
for(var/D in metric.departments)
|
||||
dat += "<tr>"
|
||||
dat += "<td>[D]</td>"
|
||||
dat += "<td>[metric.assess_department(D)]%</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
dat += "<tr>"
|
||||
dat += "<th colspan='2'>Players</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
for(var/mob/M as anything in player_list)
|
||||
dat += "<tr>"
|
||||
dat += "<td>[M] ([M.ckey])</td>"
|
||||
dat += "<td>[metric.assess_player_activity(M)]%</td>"
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
|
||||
dat += "<h2>Events available:</h2>"
|
||||
|
||||
dat += "<table border='1' style='width:100%'>"
|
||||
dat += "<tr>"
|
||||
dat += "<th>Event Name</th>"
|
||||
dat += "<th>Involved Departments</th>"
|
||||
dat += "<th>Chaos</th>"
|
||||
dat += "<th>Chaotic Threshold</th>"
|
||||
dat += "<th>Weight</th>"
|
||||
dat += "<th>Buttons</th>"
|
||||
dat += "</tr>"
|
||||
|
||||
for(var/datum/event2/meta/event as anything in available_events)
|
||||
dat += "<tr>"
|
||||
if(!event.enabled)
|
||||
dat += "<td><strike>[event.name]</strike></td>"
|
||||
else
|
||||
dat += "<td>[event.name]</td>"
|
||||
dat += "<td>[english_list(event.departments)]</td>"
|
||||
dat += "<td>[event.chaos]</td>"
|
||||
dat += "<td>[event.chaotic_threshold]</td>"
|
||||
dat += "<td>[event.get_weight()]</td>"
|
||||
dat += "<td>[href(event, list("force" = 1), "\[Force\]")] [href(event, list("toggle" = 1), "\[Toggle\]")]</td>"
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
|
||||
dat += "<h2>Events active:</h2>"
|
||||
|
||||
dat += "Current time: [world.time]"
|
||||
dat += "<table border='1' style='width:100%'>"
|
||||
dat += "<tr>"
|
||||
dat += "<th>Event Type</th>"
|
||||
dat += "<th>Time Started</th>"
|
||||
dat += "<th>Time to Announce</th>"
|
||||
dat += "<th>Time to End</th>"
|
||||
dat += "<th>Announced</th>"
|
||||
dat += "<th>Started</th>"
|
||||
dat += "<th>Ended</th>"
|
||||
dat += "<th>Buttons</th>"
|
||||
dat += "</tr>"
|
||||
|
||||
for(var/datum/event2/event/event as anything in SSevent_ticker.active_events)
|
||||
dat += "<tr>"
|
||||
dat += "<td>[event.type]</td>"
|
||||
dat += "<td>[event.time_started]</td>"
|
||||
dat += "<td>[event.time_to_announce ? event.time_to_announce : "NULL"]</td>"
|
||||
dat += "<td>[event.time_to_end ? event.time_to_end : "NULL"]</td>"
|
||||
dat += "<td>[event.announced ? "Yes" : "No"]</td>"
|
||||
dat += "<td>[event.started ? "Yes" : "No"]</td>"
|
||||
dat += "<td>[event.ended ? "Yes" : "No"]</td>"
|
||||
dat += "<td>[href(event, list("abort" = 1), "\[Abort\]")]</td>"
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
dat += "</body></html>"
|
||||
|
||||
dat += "<h2>Events completed:</h2>"
|
||||
|
||||
dat += "<table border='1' style='width:100%'>"
|
||||
dat += "<tr>"
|
||||
dat += "<th>Event Type</th>"
|
||||
dat += "<th>Start Time</th>"
|
||||
dat += "<th>Finish Time</th>"
|
||||
dat += "</tr>"
|
||||
|
||||
for(var/datum/event2/event/event as anything in SSevent_ticker.finished_events)
|
||||
dat += "<tr>"
|
||||
dat += "<td>[event.type]</td>"
|
||||
dat += "<td>[event.time_started]</td>"
|
||||
dat += "<td>[event.time_finished]</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
dat += "</body></html>"
|
||||
|
||||
var/datum/browser/popup = new(user, "game_master_debug", "Automated Game Master Event System", 800, 500, src)
|
||||
popup.set_content(dat.Join())
|
||||
popup.open()
|
||||
|
||||
|
||||
/datum/controller/subsystem/game_master/Topic(href, href_list)
|
||||
if(..())
|
||||
return
|
||||
|
||||
if(href_list["close"]) // Needed or the window re-opens after closing, making it last forever.
|
||||
return
|
||||
|
||||
if(!check_rights(R_ADMIN|R_EVENT|R_DEBUG))
|
||||
message_admins("[usr] has attempted to modify the Game Master values without sufficent privilages.")
|
||||
return
|
||||
|
||||
if(href_list["toggle"])
|
||||
can_fire = !can_fire
|
||||
message_admins("GM was [!can_fire ? "dis" : "en"]abled by [usr.key].")
|
||||
|
||||
if(href_list["toggle_time_restrictions"])
|
||||
GM.ignore_time_restrictions = !GM.ignore_time_restrictions
|
||||
message_admins("GM event time restrictions was [GM.ignore_time_restrictions ? "dis" : "en"]abled by [usr.key].")
|
||||
|
||||
if(href_list["toggle_chaos_throttle"])
|
||||
GM.ignore_round_chaos = !GM.ignore_round_chaos
|
||||
message_admins("GM event chaos restrictions was [GM.ignore_round_chaos ? "dis" : "en"]abled by [usr.key].")
|
||||
|
||||
if(href_list["force_choose_event"])
|
||||
do_event_decision()
|
||||
message_admins("[usr.key] forced the Game Master to choose an event immediately.")
|
||||
|
||||
if(href_list["change_gm"])
|
||||
choose_game_master(usr)
|
||||
|
||||
if(href_list["set_staleness"])
|
||||
var/amount = tgui_input_number(usr, "How much staleness should there be?", "Game Master")
|
||||
if(!isnull(amount))
|
||||
staleness = amount
|
||||
message_admins("GM staleness was set to [amount] by [usr.key].")
|
||||
|
||||
if(href_list["set_danger"])
|
||||
var/amount = tgui_input_number(usr, "How much danger should there be?", "Game Master")
|
||||
if(!isnull(amount))
|
||||
danger = amount
|
||||
message_admins("GM danger was set to [amount] by [usr.key].")
|
||||
|
||||
interact(usr) // To refresh the UI.
|
||||
// This is a sort of successor to the various event systems created over the years. It is designed to be just a tad smarter than the
|
||||
// previous ones, checking various things like player count, department size and composition, individual player activity,
|
||||
// individual player (IC) skill, and such, in order to try to choose the best events to take in order to add spice or variety to
|
||||
// the round.
|
||||
|
||||
// This subsystem holds the logic that chooses events. Actual event processing is handled in a seperate subsystem.
|
||||
SUBSYSTEM_DEF(game_master)
|
||||
name = "Events (Game Master)"
|
||||
wait = 1 MINUTE
|
||||
runlevels = RUNLEVEL_GAME
|
||||
|
||||
// The GM object is what actually chooses events.
|
||||
// It's held in a seperate object for better encapsulation, and allows for different 'flavors' of GMs to be made, that choose events differently.
|
||||
var/datum/game_master/GM = null
|
||||
var/game_master_type = /datum/game_master/default
|
||||
|
||||
var/list/available_events = list() // A list of meta event objects.
|
||||
|
||||
var/danger = 0 // The GM's best guess at how chaotic the round is. High danger makes it hold back.
|
||||
var/staleness = -20 // Determines liklihood of the GM doing something, increases over time.
|
||||
|
||||
var/next_event = 0 // Minimum amount of time of nothingness until the GM can pick something again.
|
||||
|
||||
var/debug_messages = FALSE // If true, debug information is written to `log_debug()`.
|
||||
|
||||
/datum/controller/subsystem/game_master/Initialize()
|
||||
var/list/subtypes = subtypesof(/datum/event2/meta)
|
||||
for(var/T in subtypes)
|
||||
var/datum/event2/meta/M = new T()
|
||||
if(!M.name)
|
||||
continue
|
||||
available_events += M
|
||||
|
||||
GM = new game_master_type()
|
||||
|
||||
if(config && !config.enable_game_master)
|
||||
can_fire = FALSE
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/game_master/fire(resumed)
|
||||
adjust_staleness(1)
|
||||
adjust_danger(-1)
|
||||
|
||||
var/global_afk = metric.assess_all_living_mobs()
|
||||
global_afk = abs(global_afk - 100)
|
||||
global_afk = round(global_afk / 100, 0.1)
|
||||
adjust_staleness(global_afk) // Staleness increases faster if more people are less active.
|
||||
|
||||
if(GM.ignore_time_restrictions || next_event < world.time)
|
||||
if(prob(staleness) && pre_event_checks())
|
||||
do_event_decision()
|
||||
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/do_event_decision()
|
||||
log_game_master("Going to choose an event.")
|
||||
var/datum/event2/meta/event_picked = GM.choose_event()
|
||||
if(event_picked)
|
||||
run_event(event_picked)
|
||||
next_event = world.time + rand(GM.decision_cooldown_lower_bound, GM.decision_cooldown_upper_bound)
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/debug_gm()
|
||||
can_fire = TRUE
|
||||
staleness = 100
|
||||
debug_messages = TRUE
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/run_event(datum/event2/meta/chosen_event)
|
||||
var/datum/event2/event/E = chosen_event.make_event()
|
||||
|
||||
chosen_event.times_ran++
|
||||
|
||||
if(!chosen_event.reusable)
|
||||
// Disable this event, so it only gets picked once.
|
||||
chosen_event.enabled = FALSE
|
||||
if(chosen_event.event_class)
|
||||
// Disable similar events, too.
|
||||
for(var/datum/event2/meta/meta as anything in available_events)
|
||||
if(meta.event_class == chosen_event.event_class)
|
||||
meta.enabled = FALSE
|
||||
|
||||
SSevent_ticker.event_started(E)
|
||||
adjust_danger(chosen_event.chaos)
|
||||
adjust_staleness(-(10 + chosen_event.chaos)) // More chaotic events reduce staleness more, e.g. a 25 chaos event will reduce it by 35.
|
||||
|
||||
|
||||
// Tell the game master that something dangerous happened, e.g. someone dying, station explosions.
|
||||
/datum/controller/subsystem/game_master/proc/adjust_danger(amount)
|
||||
amount *= GM.danger_modifier
|
||||
danger = round(between(0, danger + amount, 1000), 0.1)
|
||||
|
||||
// Tell the game master that things are getting boring if positive, or something interesting if negative..
|
||||
/datum/controller/subsystem/game_master/proc/adjust_staleness(amount)
|
||||
amount *= GM.staleness_modifier
|
||||
staleness = round( between(-20, staleness + amount, 100), 0.1)
|
||||
|
||||
// These are ran before committing to an event.
|
||||
// Returns TRUE if the system is allowed to procede, otherwise returns FALSE.
|
||||
/datum/controller/subsystem/game_master/proc/pre_event_checks(quiet = FALSE)
|
||||
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
|
||||
if(!quiet)
|
||||
log_game_master("Unable to start event: Ticker is nonexistent, or the game is not ongoing.")
|
||||
return FALSE
|
||||
if(GM.ignore_time_restrictions)
|
||||
return TRUE
|
||||
if(next_event > world.time) // Sanity.
|
||||
if(!quiet)
|
||||
log_game_master("Unable to start event: Time until next event is approximately [round((next_event - world.time) / (1 MINUTE))] minute(s)")
|
||||
return FALSE
|
||||
|
||||
// Last minute antagging is bad for humans to do, so the GM will respect the start and end of the round.
|
||||
var/mills = round_duration_in_ds
|
||||
var/mins = round((mills % 36000) / 600)
|
||||
var/hours = round(mills / 36000)
|
||||
|
||||
// if(hours < 1 && mins <= 20) // Don't do anything for the first twenty minutes of the round.
|
||||
// if(!quiet)
|
||||
// log_debug("Game Master unable to start event: It is too early.")
|
||||
// return FALSE
|
||||
if(hours >= 2 && mins >= 40) // Don't do anything in the last twenty minutes of the round, as well.
|
||||
if(!quiet)
|
||||
log_game_master("Unable to start event: It is too late.")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/choose_game_master(mob/user)
|
||||
var/list/subtypes = subtypesof(/datum/game_master)
|
||||
var/new_gm_path = tgui_input_list(user, "What kind of Game Master do you want?", "New Game Master", subtypes)
|
||||
if(new_gm_path)
|
||||
log_and_message_admins("has swapped the current GM ([GM.type]) for a new GM ([new_gm_path]).")
|
||||
GM = new new_gm_path(src)
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/log_game_master(message)
|
||||
if(debug_messages)
|
||||
log_debug("GAME MASTER: [message]")
|
||||
|
||||
|
||||
// This object makes the actual decisions.
|
||||
/datum/game_master
|
||||
// Multiplier for how much 'danger' is accumulated. Higer generally makes it possible for more dangerous events to be picked.
|
||||
var/danger_modifier = 1.0
|
||||
|
||||
// Ditto. Higher numbers generally result in more events occuring in a round.
|
||||
var/staleness_modifier = 1.0
|
||||
|
||||
var/decision_cooldown_lower_bound = 5 MINUTES // Lower bound for how long to wait until -the potential- for another event being decided.
|
||||
var/decision_cooldown_upper_bound = 20 MINUTES // Same, but upper bound.
|
||||
|
||||
var/ignore_time_restrictions = FALSE // Useful for debugging without needing to wait 20 minutes each time.
|
||||
var/ignore_round_chaos = FALSE // If true, the system will happily choose back to back intense events like meteors and blobs, Dwarf Fortress style.
|
||||
|
||||
/client/proc/show_gm_status()
|
||||
set category = "Debug"
|
||||
set name = "Show GM Status"
|
||||
set desc = "Shows you what the GM is thinking. If only that existed in real life..."
|
||||
|
||||
if(check_rights(R_ADMIN|R_EVENT|R_DEBUG))
|
||||
SSgame_master.interact(usr)
|
||||
else
|
||||
to_chat(usr, span("warning", "You do not have sufficient rights to view the GM panel, sorry."))
|
||||
|
||||
/datum/controller/subsystem/game_master/proc/interact(var/client/user)
|
||||
if(!user)
|
||||
return
|
||||
|
||||
// Using lists for string tree conservation.
|
||||
var/list/dat = list("<html><head><title>Automated Game Master Event System</title></head><body>")
|
||||
|
||||
// Makes the system turn on or off.
|
||||
dat += href(src, list("toggle" = 1), "\[Toggle GM\]")
|
||||
dat += " | "
|
||||
|
||||
// Makes the system not care about staleness or being near round-end.
|
||||
dat += href(src, list("toggle_time_restrictions" = 1), "\[Toggle Time Restrictions\]")
|
||||
dat += " | "
|
||||
|
||||
// Makes the system not care about how chaotic the round might be.
|
||||
dat += href(src, list("toggle_chaos_throttle" = 1), "\[Toggle Chaos Throttling\]")
|
||||
dat += " | "
|
||||
|
||||
// Makes the system immediately choose an event, while still bound to factors like danger, weights, and department staffing.
|
||||
dat += href(src, list("force_choose_event" = 1), "\[Force Event Decision\]")
|
||||
dat += "<br>"
|
||||
|
||||
// Swaps out the current GM for a new one with different ideas on what a good event might be.
|
||||
dat += href(src, list("change_gm" = 1), "\[Change GM\]")
|
||||
dat += "<br>"
|
||||
|
||||
dat += "Current GM Type: [GM.type]<br>"
|
||||
dat += "State: [can_fire ? "Active": "Inactive"]<br>"
|
||||
dat += "Status: [pre_event_checks(TRUE) ? "Ready" : "Suppressed"]<br><br>"
|
||||
|
||||
dat += "Staleness: [staleness] "
|
||||
dat += href(src, list("set_staleness" = 1), "\[Set\]")
|
||||
dat += "<br>"
|
||||
dat += "<i>Staleness is an estimate of how boring the round might be, and if an event should be done. It is increased passively over time, \
|
||||
and increases faster if people are AFK. It deceases when events and certain 'interesting' things happen in the round.</i><br>"
|
||||
|
||||
dat += "Danger: [danger] "
|
||||
dat += href(src, list("set_danger" = 1), "\[Set\]")
|
||||
dat += "<br>"
|
||||
dat += "<i>Danger is an estimate of how chaotic the round has been so far. It is decreased passively over time, and is increased by having \
|
||||
certain chaotic events be selected, or chaotic things happen in the round. A sufficently high amount of danger will make the system \
|
||||
avoid using destructive events, to avoid pushing the station over the edge.</i><br>"
|
||||
|
||||
dat += "<h2>Player Activity:</h2>"
|
||||
|
||||
dat += "<table border='1' style='width:100%'>"
|
||||
dat += "<tr>"
|
||||
dat += "<th>Category</th>"
|
||||
dat += "<th>Activity Percentage</th>"
|
||||
dat += "</tr>"
|
||||
|
||||
dat += "<tr>"
|
||||
dat += "<td>All Living Mobs</td>"
|
||||
dat += "<td>[metric.assess_all_living_mobs()]%</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
dat += "<tr>"
|
||||
dat += "<td>All Ghosts</td>"
|
||||
dat += "<td>[metric.assess_all_dead_mobs()]%</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
dat += "<tr>"
|
||||
dat += "<th colspan='2'>Departments</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
for(var/D in metric.departments)
|
||||
dat += "<tr>"
|
||||
dat += "<td>[D]</td>"
|
||||
dat += "<td>[metric.assess_department(D)]%</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
dat += "<tr>"
|
||||
dat += "<th colspan='2'>Players</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
for(var/mob/M as anything in player_list)
|
||||
dat += "<tr>"
|
||||
dat += "<td>[M] ([M.ckey])</td>"
|
||||
dat += "<td>[metric.assess_player_activity(M)]%</td>"
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
|
||||
dat += "<h2>Events available:</h2>"
|
||||
|
||||
dat += "<table border='1' style='width:100%'>"
|
||||
dat += "<tr>"
|
||||
dat += "<th>Event Name</th>"
|
||||
dat += "<th>Involved Departments</th>"
|
||||
dat += "<th>Chaos</th>"
|
||||
dat += "<th>Chaotic Threshold</th>"
|
||||
dat += "<th>Weight</th>"
|
||||
dat += "<th>Buttons</th>"
|
||||
dat += "</tr>"
|
||||
|
||||
for(var/datum/event2/meta/event as anything in available_events)
|
||||
dat += "<tr>"
|
||||
if(!event.enabled)
|
||||
dat += "<td><strike>[event.name]</strike></td>"
|
||||
else
|
||||
dat += "<td>[event.name]</td>"
|
||||
dat += "<td>[english_list(event.departments)]</td>"
|
||||
dat += "<td>[event.chaos]</td>"
|
||||
dat += "<td>[event.chaotic_threshold]</td>"
|
||||
dat += "<td>[event.get_weight()]</td>"
|
||||
dat += "<td>[href(event, list("force" = 1), "\[Force\]")] [href(event, list("toggle" = 1), "\[Toggle\]")]</td>"
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
|
||||
dat += "<h2>Events active:</h2>"
|
||||
|
||||
dat += "Current time: [world.time]"
|
||||
dat += "<table border='1' style='width:100%'>"
|
||||
dat += "<tr>"
|
||||
dat += "<th>Event Type</th>"
|
||||
dat += "<th>Time Started</th>"
|
||||
dat += "<th>Time to Announce</th>"
|
||||
dat += "<th>Time to End</th>"
|
||||
dat += "<th>Announced</th>"
|
||||
dat += "<th>Started</th>"
|
||||
dat += "<th>Ended</th>"
|
||||
dat += "<th>Buttons</th>"
|
||||
dat += "</tr>"
|
||||
|
||||
for(var/datum/event2/event/event as anything in SSevent_ticker.active_events)
|
||||
dat += "<tr>"
|
||||
dat += "<td>[event.type]</td>"
|
||||
dat += "<td>[event.time_started]</td>"
|
||||
dat += "<td>[event.time_to_announce ? event.time_to_announce : "NULL"]</td>"
|
||||
dat += "<td>[event.time_to_end ? event.time_to_end : "NULL"]</td>"
|
||||
dat += "<td>[event.announced ? "Yes" : "No"]</td>"
|
||||
dat += "<td>[event.started ? "Yes" : "No"]</td>"
|
||||
dat += "<td>[event.ended ? "Yes" : "No"]</td>"
|
||||
dat += "<td>[href(event, list("abort" = 1), "\[Abort\]")]</td>"
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
dat += "</body></html>"
|
||||
|
||||
dat += "<h2>Events completed:</h2>"
|
||||
|
||||
dat += "<table border='1' style='width:100%'>"
|
||||
dat += "<tr>"
|
||||
dat += "<th>Event Type</th>"
|
||||
dat += "<th>Start Time</th>"
|
||||
dat += "<th>Finish Time</th>"
|
||||
dat += "</tr>"
|
||||
|
||||
for(var/datum/event2/event/event as anything in SSevent_ticker.finished_events)
|
||||
dat += "<tr>"
|
||||
dat += "<td>[event.type]</td>"
|
||||
dat += "<td>[event.time_started]</td>"
|
||||
dat += "<td>[event.time_finished]</td>"
|
||||
dat += "</tr>"
|
||||
|
||||
dat += "</body></html>"
|
||||
|
||||
var/datum/browser/popup = new(user, "game_master_debug", "Automated Game Master Event System", 800, 500, src)
|
||||
popup.set_content(dat.Join())
|
||||
popup.open()
|
||||
|
||||
|
||||
/datum/controller/subsystem/game_master/Topic(href, href_list)
|
||||
if(..())
|
||||
return
|
||||
|
||||
if(href_list["close"]) // Needed or the window re-opens after closing, making it last forever.
|
||||
return
|
||||
|
||||
if(!check_rights(R_ADMIN|R_EVENT|R_DEBUG))
|
||||
message_admins("[usr] has attempted to modify the Game Master values without sufficent privilages.")
|
||||
return
|
||||
|
||||
if(href_list["toggle"])
|
||||
can_fire = !can_fire
|
||||
message_admins("GM was [!can_fire ? "dis" : "en"]abled by [usr.key].")
|
||||
|
||||
if(href_list["toggle_time_restrictions"])
|
||||
GM.ignore_time_restrictions = !GM.ignore_time_restrictions
|
||||
message_admins("GM event time restrictions was [GM.ignore_time_restrictions ? "dis" : "en"]abled by [usr.key].")
|
||||
|
||||
if(href_list["toggle_chaos_throttle"])
|
||||
GM.ignore_round_chaos = !GM.ignore_round_chaos
|
||||
message_admins("GM event chaos restrictions was [GM.ignore_round_chaos ? "dis" : "en"]abled by [usr.key].")
|
||||
|
||||
if(href_list["force_choose_event"])
|
||||
do_event_decision()
|
||||
message_admins("[usr.key] forced the Game Master to choose an event immediately.")
|
||||
|
||||
if(href_list["change_gm"])
|
||||
choose_game_master(usr)
|
||||
|
||||
if(href_list["set_staleness"])
|
||||
var/amount = tgui_input_number(usr, "How much staleness should there be?", "Game Master")
|
||||
if(!isnull(amount))
|
||||
staleness = amount
|
||||
message_admins("GM staleness was set to [amount] by [usr.key].")
|
||||
|
||||
if(href_list["set_danger"])
|
||||
var/amount = tgui_input_number(usr, "How much danger should there be?", "Game Master")
|
||||
if(!isnull(amount))
|
||||
danger = amount
|
||||
message_admins("GM danger was set to [amount] by [usr.key].")
|
||||
|
||||
interact(usr) // To refresh the UI.
|
||||
|
||||
+143
-143
@@ -1,143 +1,143 @@
|
||||
SUBSYSTEM_DEF(job)
|
||||
name = "Job"
|
||||
init_order = INIT_ORDER_JOB
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
var/list/occupations = list() //List of all jobs
|
||||
var/list/datum/job/name_occupations = list() //Dict of all jobs, keys are titles
|
||||
var/list/type_occupations = list() //Dict of all jobs, keys are types
|
||||
|
||||
var/list/department_datums = list()
|
||||
var/debug_messages = FALSE
|
||||
|
||||
|
||||
/datum/controller/subsystem/job/Initialize(timeofday)
|
||||
if(!department_datums.len)
|
||||
setup_departments()
|
||||
if(!occupations.len)
|
||||
setup_occupations()
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/job/proc/setup_occupations(faction = "Station")
|
||||
occupations = list()
|
||||
var/list/all_jobs = subtypesof(/datum/job)
|
||||
if(!all_jobs.len)
|
||||
to_chat(world, span("warning", "Error setting up jobs, no job datums found"))
|
||||
return FALSE
|
||||
|
||||
for(var/J in all_jobs)
|
||||
var/datum/job/job = new J()
|
||||
if(!job)
|
||||
continue
|
||||
if(job.faction != faction)
|
||||
continue
|
||||
occupations += job
|
||||
name_occupations[job.title] = job
|
||||
type_occupations[J] = job
|
||||
if(LAZYLEN(job.departments))
|
||||
add_to_departments(job)
|
||||
|
||||
sortTim(occupations, GLOBAL_PROC_REF(cmp_job_datums))
|
||||
for(var/D in department_datums)
|
||||
var/datum/department/dept = department_datums[D]
|
||||
sortTim(dept.jobs, GLOBAL_PROC_REF(cmp_job_datums), TRUE)
|
||||
sortTim(dept.primary_jobs, GLOBAL_PROC_REF(cmp_job_datums), TRUE)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/job/proc/add_to_departments(datum/job/J)
|
||||
// Adds to the regular job lists in the departments, which allow multiple departments for a job.
|
||||
for(var/D in J.departments)
|
||||
var/datum/department/dept = LAZYACCESS(department_datums, D)
|
||||
if(!istype(dept))
|
||||
job_debug_message("Job '[J.title]' is defined as being inside department '[D]', but it does not exist.")
|
||||
continue
|
||||
dept.jobs[J.title] = J
|
||||
|
||||
// Now for the 'primary' department for a job, which is defined as being the first department in the list for a job.
|
||||
// This results in no duplicates, which can be useful in some situations.
|
||||
if(LAZYLEN(J.departments))
|
||||
var/primary_department = J.departments[1]
|
||||
var/datum/department/dept = LAZYACCESS(department_datums, primary_department)
|
||||
if(!istype(dept))
|
||||
job_debug_message("Job '[J.title]' has their primary department be '[primary_department]', but it does not exist.")
|
||||
else
|
||||
dept.primary_jobs[J.title] = J
|
||||
|
||||
/datum/controller/subsystem/job/proc/setup_departments()
|
||||
for(var/t in subtypesof(/datum/department))
|
||||
var/datum/department/D = new t()
|
||||
department_datums[D.name] = D
|
||||
|
||||
sortTim(department_datums, GLOBAL_PROC_REF(cmp_department_datums), TRUE)
|
||||
|
||||
/datum/controller/subsystem/job/proc/get_all_department_datums()
|
||||
var/list/dept_datums = list()
|
||||
for(var/D in department_datums)
|
||||
dept_datums += department_datums[D]
|
||||
return dept_datums
|
||||
|
||||
/datum/controller/subsystem/job/proc/get_job(rank)
|
||||
if(!occupations.len)
|
||||
setup_occupations()
|
||||
return name_occupations[rank]
|
||||
|
||||
/datum/controller/subsystem/job/proc/get_job_type(jobtype)
|
||||
if(!occupations.len)
|
||||
setup_occupations()
|
||||
return type_occupations[jobtype]
|
||||
|
||||
// Determines if a job title is inside of a specific department.
|
||||
// Useful to replace the old `if(job_title in command_positions)` code.
|
||||
/datum/controller/subsystem/job/proc/is_job_in_department(rank, target_department_name)
|
||||
var/datum/department/D = LAZYACCESS(department_datums, target_department_name)
|
||||
if(istype(D))
|
||||
return LAZYFIND(D.jobs, rank) ? TRUE : FALSE
|
||||
return FALSE
|
||||
|
||||
// Returns a list of all job names in a specific department.
|
||||
/datum/controller/subsystem/job/proc/get_job_titles_in_department(target_department_name)
|
||||
var/datum/department/D = LAZYACCESS(department_datums, target_department_name)
|
||||
if(istype(D))
|
||||
var/list/job_titles = list()
|
||||
for(var/J in D.jobs)
|
||||
job_titles += J
|
||||
return job_titles
|
||||
|
||||
job_debug_message("Was asked to get job titles for a non-existant department '[target_department_name]'.")
|
||||
return list()
|
||||
|
||||
// Returns a reference to the primary department datum that a job is in.
|
||||
// Can receive job datum refs, typepaths, or job title strings.
|
||||
/datum/controller/subsystem/job/proc/get_primary_department_of_job(datum/job/J)
|
||||
if(!istype(J, /datum/job))
|
||||
if(ispath(J))
|
||||
J = get_job_type(J)
|
||||
else if(istext(J))
|
||||
J = get_job(J)
|
||||
|
||||
if(!istype(J))
|
||||
job_debug_message("Was asked to get department for job '[J]', but input could not be resolved into a job datum.")
|
||||
return
|
||||
|
||||
if(!LAZYLEN(J.departments))
|
||||
return
|
||||
|
||||
var/primary_department = J.departments[1]
|
||||
var/datum/department/dept = LAZYACCESS(department_datums, primary_department)
|
||||
if(!istype(dept))
|
||||
job_debug_message("Job '[J.title]' has their primary department be '[primary_department]', but it does not exist.")
|
||||
return
|
||||
|
||||
return department_datums[primary_department]
|
||||
|
||||
/datum/controller/subsystem/job/proc/get_ping_role(var/role)
|
||||
var/datum/job/J = get_job(role)
|
||||
if(J.requestable)
|
||||
return get_primary_department_of_job(J)
|
||||
|
||||
// Someday it might be good to port code/game/jobs/job_controller.dm to here and clean it up.
|
||||
|
||||
/datum/controller/subsystem/job/proc/job_debug_message(message)
|
||||
if(debug_messages)
|
||||
log_debug("JOB DEBUG: [message]")
|
||||
SUBSYSTEM_DEF(job)
|
||||
name = "Job"
|
||||
init_order = INIT_ORDER_JOB
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
var/list/occupations = list() //List of all jobs
|
||||
var/list/datum/job/name_occupations = list() //Dict of all jobs, keys are titles
|
||||
var/list/type_occupations = list() //Dict of all jobs, keys are types
|
||||
|
||||
var/list/department_datums = list()
|
||||
var/debug_messages = FALSE
|
||||
|
||||
|
||||
/datum/controller/subsystem/job/Initialize(timeofday)
|
||||
if(!department_datums.len)
|
||||
setup_departments()
|
||||
if(!occupations.len)
|
||||
setup_occupations()
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/job/proc/setup_occupations(faction = "Station")
|
||||
occupations = list()
|
||||
var/list/all_jobs = subtypesof(/datum/job)
|
||||
if(!all_jobs.len)
|
||||
to_chat(world, span("warning", "Error setting up jobs, no job datums found"))
|
||||
return FALSE
|
||||
|
||||
for(var/J in all_jobs)
|
||||
var/datum/job/job = new J()
|
||||
if(!job)
|
||||
continue
|
||||
if(job.faction != faction)
|
||||
continue
|
||||
occupations += job
|
||||
name_occupations[job.title] = job
|
||||
type_occupations[J] = job
|
||||
if(LAZYLEN(job.departments))
|
||||
add_to_departments(job)
|
||||
|
||||
sortTim(occupations, GLOBAL_PROC_REF(cmp_job_datums))
|
||||
for(var/D in department_datums)
|
||||
var/datum/department/dept = department_datums[D]
|
||||
sortTim(dept.jobs, GLOBAL_PROC_REF(cmp_job_datums), TRUE)
|
||||
sortTim(dept.primary_jobs, GLOBAL_PROC_REF(cmp_job_datums), TRUE)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/job/proc/add_to_departments(datum/job/J)
|
||||
// Adds to the regular job lists in the departments, which allow multiple departments for a job.
|
||||
for(var/D in J.departments)
|
||||
var/datum/department/dept = LAZYACCESS(department_datums, D)
|
||||
if(!istype(dept))
|
||||
job_debug_message("Job '[J.title]' is defined as being inside department '[D]', but it does not exist.")
|
||||
continue
|
||||
dept.jobs[J.title] = J
|
||||
|
||||
// Now for the 'primary' department for a job, which is defined as being the first department in the list for a job.
|
||||
// This results in no duplicates, which can be useful in some situations.
|
||||
if(LAZYLEN(J.departments))
|
||||
var/primary_department = J.departments[1]
|
||||
var/datum/department/dept = LAZYACCESS(department_datums, primary_department)
|
||||
if(!istype(dept))
|
||||
job_debug_message("Job '[J.title]' has their primary department be '[primary_department]', but it does not exist.")
|
||||
else
|
||||
dept.primary_jobs[J.title] = J
|
||||
|
||||
/datum/controller/subsystem/job/proc/setup_departments()
|
||||
for(var/t in subtypesof(/datum/department))
|
||||
var/datum/department/D = new t()
|
||||
department_datums[D.name] = D
|
||||
|
||||
sortTim(department_datums, GLOBAL_PROC_REF(cmp_department_datums), TRUE)
|
||||
|
||||
/datum/controller/subsystem/job/proc/get_all_department_datums()
|
||||
var/list/dept_datums = list()
|
||||
for(var/D in department_datums)
|
||||
dept_datums += department_datums[D]
|
||||
return dept_datums
|
||||
|
||||
/datum/controller/subsystem/job/proc/get_job(rank)
|
||||
if(!occupations.len)
|
||||
setup_occupations()
|
||||
return name_occupations[rank]
|
||||
|
||||
/datum/controller/subsystem/job/proc/get_job_type(jobtype)
|
||||
if(!occupations.len)
|
||||
setup_occupations()
|
||||
return type_occupations[jobtype]
|
||||
|
||||
// Determines if a job title is inside of a specific department.
|
||||
// Useful to replace the old `if(job_title in command_positions)` code.
|
||||
/datum/controller/subsystem/job/proc/is_job_in_department(rank, target_department_name)
|
||||
var/datum/department/D = LAZYACCESS(department_datums, target_department_name)
|
||||
if(istype(D))
|
||||
return LAZYFIND(D.jobs, rank) ? TRUE : FALSE
|
||||
return FALSE
|
||||
|
||||
// Returns a list of all job names in a specific department.
|
||||
/datum/controller/subsystem/job/proc/get_job_titles_in_department(target_department_name)
|
||||
var/datum/department/D = LAZYACCESS(department_datums, target_department_name)
|
||||
if(istype(D))
|
||||
var/list/job_titles = list()
|
||||
for(var/J in D.jobs)
|
||||
job_titles += J
|
||||
return job_titles
|
||||
|
||||
job_debug_message("Was asked to get job titles for a non-existant department '[target_department_name]'.")
|
||||
return list()
|
||||
|
||||
// Returns a reference to the primary department datum that a job is in.
|
||||
// Can receive job datum refs, typepaths, or job title strings.
|
||||
/datum/controller/subsystem/job/proc/get_primary_department_of_job(datum/job/J)
|
||||
if(!istype(J, /datum/job))
|
||||
if(ispath(J))
|
||||
J = get_job_type(J)
|
||||
else if(istext(J))
|
||||
J = get_job(J)
|
||||
|
||||
if(!istype(J))
|
||||
job_debug_message("Was asked to get department for job '[J]', but input could not be resolved into a job datum.")
|
||||
return
|
||||
|
||||
if(!LAZYLEN(J.departments))
|
||||
return
|
||||
|
||||
var/primary_department = J.departments[1]
|
||||
var/datum/department/dept = LAZYACCESS(department_datums, primary_department)
|
||||
if(!istype(dept))
|
||||
job_debug_message("Job '[J.title]' has their primary department be '[primary_department]', but it does not exist.")
|
||||
return
|
||||
|
||||
return department_datums[primary_department]
|
||||
|
||||
/datum/controller/subsystem/job/proc/get_ping_role(var/role)
|
||||
var/datum/job/J = get_job(role)
|
||||
if(J.requestable)
|
||||
return get_primary_department_of_job(J)
|
||||
|
||||
// Someday it might be good to port code/game/jobs/job_controller.dm to here and clean it up.
|
||||
|
||||
/datum/controller/subsystem/job/proc/job_debug_message(message)
|
||||
if(debug_messages)
|
||||
log_debug("JOB DEBUG: [message]")
|
||||
|
||||
@@ -1,175 +1,175 @@
|
||||
// Handles map-related tasks, mostly here to ensure it does so after the MC initializes.
|
||||
SUBSYSTEM_DEF(mapping)
|
||||
name = "Mapping"
|
||||
init_order = INIT_ORDER_MAPPING
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
var/list/map_templates = list()
|
||||
var/dmm_suite/maploader = null
|
||||
var/obj/effect/landmark/engine_loader/engine_loader
|
||||
var/list/shelter_templates = list()
|
||||
|
||||
/datum/controller/subsystem/mapping/Recover()
|
||||
flags |= SS_NO_INIT // Make extra sure we don't initialize twice.
|
||||
shelter_templates = SSmapping.shelter_templates
|
||||
|
||||
/datum/controller/subsystem/mapping/Initialize(timeofday)
|
||||
if(subsystem_initialized)
|
||||
return
|
||||
world.max_z_changed() // This is to set up the player z-level list, maxz hasn't actually changed (probably)
|
||||
maploader = new()
|
||||
load_map_templates()
|
||||
|
||||
if(config.generate_map)
|
||||
// Map-gen is still very specific to the map, however putting it here should ensure it loads in the correct order.
|
||||
using_map.perform_map_generation()
|
||||
|
||||
loadEngine()
|
||||
preloadShelterTemplates() // VOREStation EDIT: Re-enable Shelter Capsules
|
||||
// Mining generation probably should be here too
|
||||
// TODO - Other stuff related to maps and areas could be moved here too. Look at /tg
|
||||
// Lateload Code related to Expedition areas.
|
||||
if(using_map) // VOREStation Edit: Re-enable this.
|
||||
loadLateMaps()
|
||||
..()
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/load_map_templates()
|
||||
for(var/datum/map_template/template as anything in subtypesof(/datum/map_template))
|
||||
if(!(initial(template.mappath))) // If it's missing the actual path its probably a base type or being used for inheritence.
|
||||
continue
|
||||
template = new template()
|
||||
map_templates[template.name] = template
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/loadEngine()
|
||||
if(!engine_loader)
|
||||
return // Seems this map doesn't need an engine loaded.
|
||||
|
||||
var/turf/T = get_turf(engine_loader)
|
||||
if(!isturf(T))
|
||||
to_world_log("[log_info_line(engine_loader)] not on a turf! Cannot place engine template.")
|
||||
return
|
||||
|
||||
// Choose an engine type
|
||||
var/datum/map_template/engine/chosen_type = null
|
||||
if (LAZYLEN(config.engine_map))
|
||||
var/chosen_name = pick(config.engine_map)
|
||||
chosen_type = map_templates[chosen_name]
|
||||
if(!istype(chosen_type))
|
||||
error("Configured engine map [chosen_name] is not a valid engine map name!")
|
||||
if(!istype(chosen_type))
|
||||
var/list/engine_types = list()
|
||||
for(var/map in map_templates)
|
||||
var/datum/map_template/engine/MT = map_templates[map]
|
||||
if(istype(MT))
|
||||
engine_types += MT
|
||||
chosen_type = pick(engine_types)
|
||||
to_world_log("Chose Engine Map: [chosen_type.name]")
|
||||
admin_notice("<span class='danger'>Chose Engine Map: [chosen_type.name]</span>", R_DEBUG)
|
||||
|
||||
// Annihilate movable atoms
|
||||
engine_loader.annihilate_bounds()
|
||||
//CHECK_TICK //Don't let anything else happen for now
|
||||
// Actually load it
|
||||
chosen_type.load(T)
|
||||
|
||||
// VOREStation Edit Start: Enable This
|
||||
/datum/controller/subsystem/mapping/proc/loadLateMaps()
|
||||
var/list/deffo_load = using_map.lateload_z_levels
|
||||
var/list/maybe_load = using_map.lateload_gateway
|
||||
var/list/also_load = using_map.lateload_overmap
|
||||
var/list/redgate_load = using_map.lateload_redgate
|
||||
|
||||
for(var/list/maplist in deffo_load)
|
||||
if(!islist(maplist))
|
||||
error("Lateload Z level [maplist] is not a list! Must be in a list!")
|
||||
continue
|
||||
for(var/mapname in maplist)
|
||||
var/datum/map_template/MT = map_templates[mapname]
|
||||
if(!istype(MT))
|
||||
error("Lateload Z level \"[mapname]\" is not a valid map!")
|
||||
continue
|
||||
admin_notice("Lateload: [MT]", R_DEBUG)
|
||||
MT.load_new_z(centered = FALSE)
|
||||
CHECK_TICK
|
||||
|
||||
if(LAZYLEN(maybe_load))
|
||||
var/picklist = pick(maybe_load)
|
||||
|
||||
if(!picklist) //No lateload maps at all
|
||||
return
|
||||
|
||||
if(!islist(picklist)) //So you can have a 'chain' of z-levels that make up one away mission
|
||||
error("Randompick Z level [picklist] is not a list! Must be in a list!")
|
||||
return
|
||||
|
||||
for(var/map in picklist)
|
||||
if(islist(map))
|
||||
// TRIPLE NEST. In this situation we pick one at random from the choices in the list.
|
||||
//This allows a sort of a1,a2,a3,b1,b2,b3,c1,c2,c3 setup where it picks one 'a', one 'b', one 'c'
|
||||
map = pick(map)
|
||||
var/datum/map_template/MT = map_templates[map]
|
||||
if(!istype(MT))
|
||||
error("Randompick Z level \"[map]\" is not a valid map!")
|
||||
else
|
||||
admin_notice("Gateway: [MT]", R_DEBUG)
|
||||
MT.load_new_z(centered = FALSE)
|
||||
|
||||
if(LAZYLEN(also_load)) //Just copied from gateway picking, this is so we can have a kind of OM map version of the same concept.
|
||||
var/picklist = pick(also_load)
|
||||
|
||||
if(!picklist) //No lateload maps at all
|
||||
return
|
||||
|
||||
if(!islist(picklist)) //So you can have a 'chain' of z-levels that make up one away mission
|
||||
error("Randompick Z level [picklist] is not a list! Must be in a list!")
|
||||
return
|
||||
|
||||
for(var/map in picklist)
|
||||
if(islist(map))
|
||||
// TRIPLE NEST. In this situation we pick one at random from the choices in the list.
|
||||
//This allows a sort of a1,a2,a3,b1,b2,b3,c1,c2,c3 setup where it picks one 'a', one 'b', one 'c'
|
||||
map = pick(map)
|
||||
var/datum/map_template/MT = map_templates[map]
|
||||
if(!istype(MT))
|
||||
error("Randompick Z level \"[map]\" is not a valid map!")
|
||||
else
|
||||
admin_notice("OM Adventure: [MT]", R_DEBUG)
|
||||
MT.load_new_z(centered = FALSE)
|
||||
|
||||
if(LAZYLEN(redgate_load))
|
||||
var/picklist = pick(redgate_load)
|
||||
|
||||
if(!picklist) //No lateload maps at all
|
||||
return
|
||||
|
||||
if(!islist(picklist)) //So you can have a 'chain' of z-levels that make up one away mission
|
||||
error("Randompick Z level [picklist] is not a list! Must be in a list!")
|
||||
return
|
||||
|
||||
for(var/map in picklist)
|
||||
if(islist(map))
|
||||
// TRIPLE NEST. In this situation we pick one at random from the choices in the list.
|
||||
//This allows a sort of a1,a2,a3,b1,b2,b3,c1,c2,c3 setup where it picks one 'a', one 'b', one 'c'
|
||||
map = pick(map)
|
||||
var/datum/map_template/MT = map_templates[map]
|
||||
if(!istype(MT))
|
||||
error("Randompick Z level \"[map]\" is not a valid map!")
|
||||
else
|
||||
admin_notice("Redgate: [MT]", R_DEBUG)
|
||||
MT.load_new_z(centered = FALSE)
|
||||
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/preloadShelterTemplates()
|
||||
for(var/datum/map_template/shelter/shelter_type as anything in subtypesof(/datum/map_template/shelter))
|
||||
if(!(initial(shelter_type.mappath)))
|
||||
continue
|
||||
var/datum/map_template/shelter/S = new shelter_type()
|
||||
|
||||
shelter_templates[S.shelter_id] = S
|
||||
// VOREStation Edit End: Re-enable this
|
||||
|
||||
/datum/controller/subsystem/mapping/stat_entry(msg)
|
||||
if (!Debug2)
|
||||
return // Only show up in stat panel if debugging is enabled.
|
||||
. = ..()
|
||||
// Handles map-related tasks, mostly here to ensure it does so after the MC initializes.
|
||||
SUBSYSTEM_DEF(mapping)
|
||||
name = "Mapping"
|
||||
init_order = INIT_ORDER_MAPPING
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
var/list/map_templates = list()
|
||||
var/dmm_suite/maploader = null
|
||||
var/obj/effect/landmark/engine_loader/engine_loader
|
||||
var/list/shelter_templates = list()
|
||||
|
||||
/datum/controller/subsystem/mapping/Recover()
|
||||
flags |= SS_NO_INIT // Make extra sure we don't initialize twice.
|
||||
shelter_templates = SSmapping.shelter_templates
|
||||
|
||||
/datum/controller/subsystem/mapping/Initialize(timeofday)
|
||||
if(subsystem_initialized)
|
||||
return
|
||||
world.max_z_changed() // This is to set up the player z-level list, maxz hasn't actually changed (probably)
|
||||
maploader = new()
|
||||
load_map_templates()
|
||||
|
||||
if(config.generate_map)
|
||||
// Map-gen is still very specific to the map, however putting it here should ensure it loads in the correct order.
|
||||
using_map.perform_map_generation()
|
||||
|
||||
loadEngine()
|
||||
preloadShelterTemplates() // VOREStation EDIT: Re-enable Shelter Capsules
|
||||
// Mining generation probably should be here too
|
||||
// TODO - Other stuff related to maps and areas could be moved here too. Look at /tg
|
||||
// Lateload Code related to Expedition areas.
|
||||
if(using_map) // VOREStation Edit: Re-enable this.
|
||||
loadLateMaps()
|
||||
..()
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/load_map_templates()
|
||||
for(var/datum/map_template/template as anything in subtypesof(/datum/map_template))
|
||||
if(!(initial(template.mappath))) // If it's missing the actual path its probably a base type or being used for inheritence.
|
||||
continue
|
||||
template = new template()
|
||||
map_templates[template.name] = template
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/loadEngine()
|
||||
if(!engine_loader)
|
||||
return // Seems this map doesn't need an engine loaded.
|
||||
|
||||
var/turf/T = get_turf(engine_loader)
|
||||
if(!isturf(T))
|
||||
to_world_log("[log_info_line(engine_loader)] not on a turf! Cannot place engine template.")
|
||||
return
|
||||
|
||||
// Choose an engine type
|
||||
var/datum/map_template/engine/chosen_type = null
|
||||
if (LAZYLEN(config.engine_map))
|
||||
var/chosen_name = pick(config.engine_map)
|
||||
chosen_type = map_templates[chosen_name]
|
||||
if(!istype(chosen_type))
|
||||
error("Configured engine map [chosen_name] is not a valid engine map name!")
|
||||
if(!istype(chosen_type))
|
||||
var/list/engine_types = list()
|
||||
for(var/map in map_templates)
|
||||
var/datum/map_template/engine/MT = map_templates[map]
|
||||
if(istype(MT))
|
||||
engine_types += MT
|
||||
chosen_type = pick(engine_types)
|
||||
to_world_log("Chose Engine Map: [chosen_type.name]")
|
||||
admin_notice("<span class='danger'>Chose Engine Map: [chosen_type.name]</span>", R_DEBUG)
|
||||
|
||||
// Annihilate movable atoms
|
||||
engine_loader.annihilate_bounds()
|
||||
//CHECK_TICK //Don't let anything else happen for now
|
||||
// Actually load it
|
||||
chosen_type.load(T)
|
||||
|
||||
// VOREStation Edit Start: Enable This
|
||||
/datum/controller/subsystem/mapping/proc/loadLateMaps()
|
||||
var/list/deffo_load = using_map.lateload_z_levels
|
||||
var/list/maybe_load = using_map.lateload_gateway
|
||||
var/list/also_load = using_map.lateload_overmap
|
||||
var/list/redgate_load = using_map.lateload_redgate
|
||||
|
||||
for(var/list/maplist in deffo_load)
|
||||
if(!islist(maplist))
|
||||
error("Lateload Z level [maplist] is not a list! Must be in a list!")
|
||||
continue
|
||||
for(var/mapname in maplist)
|
||||
var/datum/map_template/MT = map_templates[mapname]
|
||||
if(!istype(MT))
|
||||
error("Lateload Z level \"[mapname]\" is not a valid map!")
|
||||
continue
|
||||
admin_notice("Lateload: [MT]", R_DEBUG)
|
||||
MT.load_new_z(centered = FALSE)
|
||||
CHECK_TICK
|
||||
|
||||
if(LAZYLEN(maybe_load))
|
||||
var/picklist = pick(maybe_load)
|
||||
|
||||
if(!picklist) //No lateload maps at all
|
||||
return
|
||||
|
||||
if(!islist(picklist)) //So you can have a 'chain' of z-levels that make up one away mission
|
||||
error("Randompick Z level [picklist] is not a list! Must be in a list!")
|
||||
return
|
||||
|
||||
for(var/map in picklist)
|
||||
if(islist(map))
|
||||
// TRIPLE NEST. In this situation we pick one at random from the choices in the list.
|
||||
//This allows a sort of a1,a2,a3,b1,b2,b3,c1,c2,c3 setup where it picks one 'a', one 'b', one 'c'
|
||||
map = pick(map)
|
||||
var/datum/map_template/MT = map_templates[map]
|
||||
if(!istype(MT))
|
||||
error("Randompick Z level \"[map]\" is not a valid map!")
|
||||
else
|
||||
admin_notice("Gateway: [MT]", R_DEBUG)
|
||||
MT.load_new_z(centered = FALSE)
|
||||
|
||||
if(LAZYLEN(also_load)) //Just copied from gateway picking, this is so we can have a kind of OM map version of the same concept.
|
||||
var/picklist = pick(also_load)
|
||||
|
||||
if(!picklist) //No lateload maps at all
|
||||
return
|
||||
|
||||
if(!islist(picklist)) //So you can have a 'chain' of z-levels that make up one away mission
|
||||
error("Randompick Z level [picklist] is not a list! Must be in a list!")
|
||||
return
|
||||
|
||||
for(var/map in picklist)
|
||||
if(islist(map))
|
||||
// TRIPLE NEST. In this situation we pick one at random from the choices in the list.
|
||||
//This allows a sort of a1,a2,a3,b1,b2,b3,c1,c2,c3 setup where it picks one 'a', one 'b', one 'c'
|
||||
map = pick(map)
|
||||
var/datum/map_template/MT = map_templates[map]
|
||||
if(!istype(MT))
|
||||
error("Randompick Z level \"[map]\" is not a valid map!")
|
||||
else
|
||||
admin_notice("OM Adventure: [MT]", R_DEBUG)
|
||||
MT.load_new_z(centered = FALSE)
|
||||
|
||||
if(LAZYLEN(redgate_load))
|
||||
var/picklist = pick(redgate_load)
|
||||
|
||||
if(!picklist) //No lateload maps at all
|
||||
return
|
||||
|
||||
if(!islist(picklist)) //So you can have a 'chain' of z-levels that make up one away mission
|
||||
error("Randompick Z level [picklist] is not a list! Must be in a list!")
|
||||
return
|
||||
|
||||
for(var/map in picklist)
|
||||
if(islist(map))
|
||||
// TRIPLE NEST. In this situation we pick one at random from the choices in the list.
|
||||
//This allows a sort of a1,a2,a3,b1,b2,b3,c1,c2,c3 setup where it picks one 'a', one 'b', one 'c'
|
||||
map = pick(map)
|
||||
var/datum/map_template/MT = map_templates[map]
|
||||
if(!istype(MT))
|
||||
error("Randompick Z level \"[map]\" is not a valid map!")
|
||||
else
|
||||
admin_notice("Redgate: [MT]", R_DEBUG)
|
||||
MT.load_new_z(centered = FALSE)
|
||||
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/preloadShelterTemplates()
|
||||
for(var/datum/map_template/shelter/shelter_type as anything in subtypesof(/datum/map_template/shelter))
|
||||
if(!(initial(shelter_type.mappath)))
|
||||
continue
|
||||
var/datum/map_template/shelter/S = new shelter_type()
|
||||
|
||||
shelter_templates[S.shelter_id] = S
|
||||
// VOREStation Edit End: Re-enable this
|
||||
|
||||
/datum/controller/subsystem/mapping/stat_entry(msg)
|
||||
if (!Debug2)
|
||||
return // Only show up in stat panel if debugging is enabled.
|
||||
. = ..()
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
/*!
|
||||
* Copyright (c) 2022 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
SUBSYSTEM_DEF(ping)
|
||||
name = "Ping"
|
||||
priority = FIRE_PRIORITY_PING
|
||||
// init_stage = INITSTAGE_EARLY
|
||||
wait = 4 SECONDS
|
||||
flags = SS_NO_INIT
|
||||
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
|
||||
var/list/currentrun = list()
|
||||
|
||||
/datum/controller/subsystem/ping/stat_entry()
|
||||
..("P:[GLOB.clients.len]")
|
||||
|
||||
/datum/controller/subsystem/ping/fire(resumed = FALSE)
|
||||
// Prepare the new batch of clients
|
||||
if (!resumed)
|
||||
src.currentrun = GLOB.clients.Copy()
|
||||
|
||||
// De-reference the list for sanic speeds
|
||||
var/list/currentrun = src.currentrun
|
||||
|
||||
while (currentrun.len)
|
||||
var/client/client = currentrun[currentrun.len]
|
||||
currentrun.len--
|
||||
|
||||
if(!client.is_preference_enabled(/datum/client_preference/vchat_enable))
|
||||
winset(client, "output", "on-show=&is-disabled=0&is-visible=1")
|
||||
winset(client, "browseroutput", "is-disabled=1;is-visible=0")
|
||||
client.tgui_panel.oldchat = TRUE
|
||||
|
||||
if (client?.tgui_panel?.is_ready())
|
||||
// Send a soft ping
|
||||
client.tgui_panel.window.send_message("ping/soft", list(
|
||||
// Slightly less than the subsystem timer (somewhat arbitrary)
|
||||
// to prevent incoming pings from resetting the afk state
|
||||
"afk" = client.is_afk(3.5 SECONDS),
|
||||
))
|
||||
|
||||
if (MC_TICK_CHECK)
|
||||
return
|
||||
/*!
|
||||
* Copyright (c) 2022 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
SUBSYSTEM_DEF(ping)
|
||||
name = "Ping"
|
||||
priority = FIRE_PRIORITY_PING
|
||||
// init_stage = INITSTAGE_EARLY
|
||||
wait = 4 SECONDS
|
||||
flags = SS_NO_INIT
|
||||
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
|
||||
var/list/currentrun = list()
|
||||
|
||||
/datum/controller/subsystem/ping/stat_entry()
|
||||
..("P:[GLOB.clients.len]")
|
||||
|
||||
/datum/controller/subsystem/ping/fire(resumed = FALSE)
|
||||
// Prepare the new batch of clients
|
||||
if (!resumed)
|
||||
src.currentrun = GLOB.clients.Copy()
|
||||
|
||||
// De-reference the list for sanic speeds
|
||||
var/list/currentrun = src.currentrun
|
||||
|
||||
while (currentrun.len)
|
||||
var/client/client = currentrun[currentrun.len]
|
||||
currentrun.len--
|
||||
|
||||
if(!client.is_preference_enabled(/datum/client_preference/vchat_enable))
|
||||
winset(client, "output", "on-show=&is-disabled=0&is-visible=1")
|
||||
winset(client, "browseroutput", "is-disabled=1;is-visible=0")
|
||||
client.tgui_panel.oldchat = TRUE
|
||||
|
||||
if (client?.tgui_panel?.is_ready())
|
||||
// Send a soft ping
|
||||
client.tgui_panel.window.send_message("ping/soft", list(
|
||||
// Slightly less than the subsystem timer (somewhat arbitrary)
|
||||
// to prevent incoming pings from resetting the afk state
|
||||
"afk" = client.is_afk(3.5 SECONDS),
|
||||
))
|
||||
|
||||
if (MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
@@ -1,187 +1,187 @@
|
||||
// This holds all the code needed to manage and use a SQLite database.
|
||||
// It is merely a file sitting inside the data directory, as opposed to a full fledged DB service,
|
||||
// however this makes it a lot easier to test, and it is natively supported by BYOND.
|
||||
SUBSYSTEM_DEF(sqlite)
|
||||
name = "SQLite"
|
||||
init_order = INIT_ORDER_SQLITE
|
||||
flags = SS_NO_FIRE
|
||||
var/database/sqlite_db = null
|
||||
|
||||
/datum/controller/subsystem/sqlite/Initialize(timeofday)
|
||||
connect()
|
||||
if(sqlite_db)
|
||||
init_schema(sqlite_db)
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/sqlite/proc/connect()
|
||||
if(!config.sqlite_enabled)
|
||||
return
|
||||
|
||||
if(!sqlite_db)
|
||||
sqlite_db = new("data/sqlite/sqlite.db") // The path has to be hardcoded or BYOND silently fails.
|
||||
|
||||
|
||||
if(!sqlite_db)
|
||||
to_world_log("Failed to load or create a SQLite database.")
|
||||
log_debug("ERROR: SQLite database is active in config but failed to load.")
|
||||
else
|
||||
to_world_log("Sqlite database connected.")
|
||||
|
||||
// Makes the tables, if they do not already exist in the sqlite file.
|
||||
/datum/controller/subsystem/sqlite/proc/init_schema(database/sqlite_object)
|
||||
// Feedback table.
|
||||
// Note that this is for direct feedback from players using the in-game feedback system and NOT for stat tracking.
|
||||
// Player ckeys are not stored in this table as a unique key due to a config option to hash the keys to encourage more honest feedback.
|
||||
/*
|
||||
* id - Primary unique key to ID a specific piece of feedback.
|
||||
NOT used to id people submitting feedback.
|
||||
* author - The person who submitted it. Will be the ckey, or a hash of the ckey,
|
||||
if both the config supports it, and the user wants it.
|
||||
* topic - A specific category to organize feedback under. Options are defined in the config file.
|
||||
* content - What the author decided to write to the staff. Limited to MAX_FEEDBACK_LENGTH.
|
||||
* datetime - When the author submitted their feedback, acts as a timestamp.
|
||||
*/
|
||||
var/database/query/init_schema = new(
|
||||
{"
|
||||
CREATE TABLE IF NOT EXISTS [SQLITE_TABLE_FEEDBACK]
|
||||
(
|
||||
`[SQLITE_FEEDBACK_COLUMN_ID]` INTEGER NOT NULL UNIQUE,
|
||||
`[SQLITE_FEEDBACK_COLUMN_AUTHOR]` TEXT NOT NULL,
|
||||
`[SQLITE_FEEDBACK_COLUMN_TOPIC]` TEXT NOT NULL,
|
||||
`[SQLITE_FEEDBACK_COLUMN_CONTENT]` TEXT NOT NULL,
|
||||
`[SQLITE_FEEDBACK_COLUMN_DATETIME]` TEXT NOT NULL,
|
||||
PRIMARY KEY(`[SQLITE_FEEDBACK_COLUMN_ID]`)
|
||||
);
|
||||
"}
|
||||
)
|
||||
init_schema.Execute(sqlite_object)
|
||||
sqlite_check_for_errors(init_schema, "Feedback table creation")
|
||||
|
||||
// Add more schemas below this if the SQLite DB gets expanded for things like persistant news, polls, bans, deaths, etc.
|
||||
|
||||
// General error checking for SQLite.
|
||||
// Returns true if something went wrong. Also writes a log.
|
||||
// The desc parameter should be unique for each call, to make it easier to track down where the error occured.
|
||||
/datum/controller/subsystem/sqlite/proc/sqlite_check_for_errors(var/database/query/query_used, var/desc)
|
||||
if(query_used && query_used.ErrorMsg())
|
||||
log_debug("SQLite Error: [desc] : [query_used.ErrorMsg()]")
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
|
||||
/************
|
||||
* Feedback *
|
||||
************/
|
||||
|
||||
// Inserts data into the feedback table in a painless manner.
|
||||
// Returns TRUE if no issues happened, FALSE otherwise.
|
||||
/datum/controller/subsystem/sqlite/proc/insert_feedback(author, topic, content, database/sqlite_object)
|
||||
if(!author || !topic || !content)
|
||||
CRASH("One or more parameters was invalid.")
|
||||
|
||||
// Sanitize everything to avoid sneaky stuff.
|
||||
var/sqlite_author = sql_sanitize_text(ckey(lowertext(author)))
|
||||
var/sqlite_content = sql_sanitize_text(content)
|
||||
var/sqlite_topic = sql_sanitize_text(topic)
|
||||
|
||||
var/database/query/query = new(
|
||||
"INSERT INTO [SQLITE_TABLE_FEEDBACK] (\
|
||||
[SQLITE_FEEDBACK_COLUMN_AUTHOR], \
|
||||
[SQLITE_FEEDBACK_COLUMN_TOPIC], \
|
||||
[SQLITE_FEEDBACK_COLUMN_CONTENT], \
|
||||
[SQLITE_FEEDBACK_COLUMN_DATETIME]) \
|
||||
\
|
||||
VALUES (\
|
||||
?,\
|
||||
?,\
|
||||
?,\
|
||||
datetime('now'))",
|
||||
sqlite_author,
|
||||
sqlite_topic,
|
||||
sqlite_content
|
||||
)
|
||||
query.Execute(sqlite_object)
|
||||
return !sqlite_check_for_errors(query, "Insert Feedback")
|
||||
|
||||
/datum/controller/subsystem/sqlite/proc/can_submit_feedback(client/C)
|
||||
if(!config.sqlite_enabled)
|
||||
return FALSE
|
||||
if(config.sqlite_feedback_min_age && !is_old_enough(C))
|
||||
return FALSE
|
||||
if(config.sqlite_feedback_cooldown > 0 && get_feedback_cooldown(C.key, config.sqlite_feedback_cooldown, sqlite_db) > 0)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
// Returns TRUE if the player is 'old' enough, according to the config.
|
||||
/datum/controller/subsystem/sqlite/proc/is_old_enough(client/C)
|
||||
if(get_player_age(C.key) < config.sqlite_feedback_min_age)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
|
||||
// Returns how many days someone has to wait, to submit more feedback, or 0 if they can do so right now.
|
||||
/datum/controller/subsystem/sqlite/proc/get_feedback_cooldown(player_ckey, cooldown, database/sqlite_object)
|
||||
player_ckey = sql_sanitize_text(ckey(lowertext(player_ckey)))
|
||||
var/potential_hashed_ckey = sql_sanitize_text(md5(player_ckey + SSsqlite.get_feedback_pepper()))
|
||||
|
||||
// First query is to get the most recent time the player has submitted feedback.
|
||||
var/database/query/query = new({"
|
||||
SELECT [SQLITE_FEEDBACK_COLUMN_DATETIME]
|
||||
FROM [SQLITE_TABLE_FEEDBACK]
|
||||
WHERE [SQLITE_FEEDBACK_COLUMN_AUTHOR] == ? OR [SQLITE_FEEDBACK_COLUMN_AUTHOR] == ?
|
||||
ORDER BY [SQLITE_FEEDBACK_COLUMN_DATETIME]
|
||||
DESC LIMIT 1;
|
||||
"},
|
||||
player_ckey,
|
||||
potential_hashed_ckey
|
||||
)
|
||||
query.Execute(sqlite_object)
|
||||
sqlite_check_for_errors(query, "Rate Limited Check 1")
|
||||
|
||||
// It is possible this is their first time, so there won't be a next row.
|
||||
if(query.NextRow()) // If this is true, the user has submitted feedback at least once.
|
||||
var/list/row_data = query.GetRowData()
|
||||
var/last_submission_datetime = row_data[SQLITE_FEEDBACK_COLUMN_DATETIME]
|
||||
|
||||
// Now we have the datetime, we need to do something to compare it.
|
||||
// Second query is to calculate the difference between two datetimes.
|
||||
// This is done on the SQLite side because parsing datetimes with BYOND is probably a bad idea.
|
||||
query = new(
|
||||
"SELECT julianday('now') - julianday(?) \
|
||||
AS 'datediff';",
|
||||
last_submission_datetime
|
||||
)
|
||||
query.Execute(sqlite_object)
|
||||
sqlite_check_for_errors(query, "Rate Limited Check 2")
|
||||
|
||||
query.NextRow()
|
||||
row_data = query.GetRowData()
|
||||
var/date_diff = row_data["datediff"]
|
||||
|
||||
// Now check if it's too soon to give more feedback.
|
||||
if(text2num(date_diff) < cooldown) // Too soon.
|
||||
return round(cooldown - date_diff, 0.1)
|
||||
return 0.0
|
||||
|
||||
|
||||
// A Pepper is like a Salt but only one exists and is supposed to be outside of a database.
|
||||
// If the file is properly protected, it can only be viewed/copied by sys-admins generating a log, which is much more conspicious than accessing/copying a DB.
|
||||
// This stops mods/admins/etc from guessing the author by shoving names in an MD5 hasher until they pick the right one.
|
||||
// Don't use this for things needing actual security.
|
||||
/datum/controller/subsystem/sqlite/proc/get_feedback_pepper()
|
||||
var/pepper_file = file2list("config/sqlite_feedback_pepper.txt")
|
||||
var/pepper = null
|
||||
for(var/line in pepper_file)
|
||||
if(!line)
|
||||
continue
|
||||
if(length(line) == 0)
|
||||
continue
|
||||
else if(copytext(line, 1, 2) == "#")
|
||||
continue
|
||||
else
|
||||
pepper = line
|
||||
break
|
||||
return pepper
|
||||
|
||||
/datum/controller/subsystem/sqlite/CanProcCall(procname)
|
||||
return procname != "get_feedback_pepper"
|
||||
// This holds all the code needed to manage and use a SQLite database.
|
||||
// It is merely a file sitting inside the data directory, as opposed to a full fledged DB service,
|
||||
// however this makes it a lot easier to test, and it is natively supported by BYOND.
|
||||
SUBSYSTEM_DEF(sqlite)
|
||||
name = "SQLite"
|
||||
init_order = INIT_ORDER_SQLITE
|
||||
flags = SS_NO_FIRE
|
||||
var/database/sqlite_db = null
|
||||
|
||||
/datum/controller/subsystem/sqlite/Initialize(timeofday)
|
||||
connect()
|
||||
if(sqlite_db)
|
||||
init_schema(sqlite_db)
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/sqlite/proc/connect()
|
||||
if(!config.sqlite_enabled)
|
||||
return
|
||||
|
||||
if(!sqlite_db)
|
||||
sqlite_db = new("data/sqlite/sqlite.db") // The path has to be hardcoded or BYOND silently fails.
|
||||
|
||||
|
||||
if(!sqlite_db)
|
||||
to_world_log("Failed to load or create a SQLite database.")
|
||||
log_debug("ERROR: SQLite database is active in config but failed to load.")
|
||||
else
|
||||
to_world_log("Sqlite database connected.")
|
||||
|
||||
// Makes the tables, if they do not already exist in the sqlite file.
|
||||
/datum/controller/subsystem/sqlite/proc/init_schema(database/sqlite_object)
|
||||
// Feedback table.
|
||||
// Note that this is for direct feedback from players using the in-game feedback system and NOT for stat tracking.
|
||||
// Player ckeys are not stored in this table as a unique key due to a config option to hash the keys to encourage more honest feedback.
|
||||
/*
|
||||
* id - Primary unique key to ID a specific piece of feedback.
|
||||
NOT used to id people submitting feedback.
|
||||
* author - The person who submitted it. Will be the ckey, or a hash of the ckey,
|
||||
if both the config supports it, and the user wants it.
|
||||
* topic - A specific category to organize feedback under. Options are defined in the config file.
|
||||
* content - What the author decided to write to the staff. Limited to MAX_FEEDBACK_LENGTH.
|
||||
* datetime - When the author submitted their feedback, acts as a timestamp.
|
||||
*/
|
||||
var/database/query/init_schema = new(
|
||||
{"
|
||||
CREATE TABLE IF NOT EXISTS [SQLITE_TABLE_FEEDBACK]
|
||||
(
|
||||
`[SQLITE_FEEDBACK_COLUMN_ID]` INTEGER NOT NULL UNIQUE,
|
||||
`[SQLITE_FEEDBACK_COLUMN_AUTHOR]` TEXT NOT NULL,
|
||||
`[SQLITE_FEEDBACK_COLUMN_TOPIC]` TEXT NOT NULL,
|
||||
`[SQLITE_FEEDBACK_COLUMN_CONTENT]` TEXT NOT NULL,
|
||||
`[SQLITE_FEEDBACK_COLUMN_DATETIME]` TEXT NOT NULL,
|
||||
PRIMARY KEY(`[SQLITE_FEEDBACK_COLUMN_ID]`)
|
||||
);
|
||||
"}
|
||||
)
|
||||
init_schema.Execute(sqlite_object)
|
||||
sqlite_check_for_errors(init_schema, "Feedback table creation")
|
||||
|
||||
// Add more schemas below this if the SQLite DB gets expanded for things like persistant news, polls, bans, deaths, etc.
|
||||
|
||||
// General error checking for SQLite.
|
||||
// Returns true if something went wrong. Also writes a log.
|
||||
// The desc parameter should be unique for each call, to make it easier to track down where the error occured.
|
||||
/datum/controller/subsystem/sqlite/proc/sqlite_check_for_errors(var/database/query/query_used, var/desc)
|
||||
if(query_used && query_used.ErrorMsg())
|
||||
log_debug("SQLite Error: [desc] : [query_used.ErrorMsg()]")
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
|
||||
/************
|
||||
* Feedback *
|
||||
************/
|
||||
|
||||
// Inserts data into the feedback table in a painless manner.
|
||||
// Returns TRUE if no issues happened, FALSE otherwise.
|
||||
/datum/controller/subsystem/sqlite/proc/insert_feedback(author, topic, content, database/sqlite_object)
|
||||
if(!author || !topic || !content)
|
||||
CRASH("One or more parameters was invalid.")
|
||||
|
||||
// Sanitize everything to avoid sneaky stuff.
|
||||
var/sqlite_author = sql_sanitize_text(ckey(lowertext(author)))
|
||||
var/sqlite_content = sql_sanitize_text(content)
|
||||
var/sqlite_topic = sql_sanitize_text(topic)
|
||||
|
||||
var/database/query/query = new(
|
||||
"INSERT INTO [SQLITE_TABLE_FEEDBACK] (\
|
||||
[SQLITE_FEEDBACK_COLUMN_AUTHOR], \
|
||||
[SQLITE_FEEDBACK_COLUMN_TOPIC], \
|
||||
[SQLITE_FEEDBACK_COLUMN_CONTENT], \
|
||||
[SQLITE_FEEDBACK_COLUMN_DATETIME]) \
|
||||
\
|
||||
VALUES (\
|
||||
?,\
|
||||
?,\
|
||||
?,\
|
||||
datetime('now'))",
|
||||
sqlite_author,
|
||||
sqlite_topic,
|
||||
sqlite_content
|
||||
)
|
||||
query.Execute(sqlite_object)
|
||||
return !sqlite_check_for_errors(query, "Insert Feedback")
|
||||
|
||||
/datum/controller/subsystem/sqlite/proc/can_submit_feedback(client/C)
|
||||
if(!config.sqlite_enabled)
|
||||
return FALSE
|
||||
if(config.sqlite_feedback_min_age && !is_old_enough(C))
|
||||
return FALSE
|
||||
if(config.sqlite_feedback_cooldown > 0 && get_feedback_cooldown(C.key, config.sqlite_feedback_cooldown, sqlite_db) > 0)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
// Returns TRUE if the player is 'old' enough, according to the config.
|
||||
/datum/controller/subsystem/sqlite/proc/is_old_enough(client/C)
|
||||
if(get_player_age(C.key) < config.sqlite_feedback_min_age)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
|
||||
// Returns how many days someone has to wait, to submit more feedback, or 0 if they can do so right now.
|
||||
/datum/controller/subsystem/sqlite/proc/get_feedback_cooldown(player_ckey, cooldown, database/sqlite_object)
|
||||
player_ckey = sql_sanitize_text(ckey(lowertext(player_ckey)))
|
||||
var/potential_hashed_ckey = sql_sanitize_text(md5(player_ckey + SSsqlite.get_feedback_pepper()))
|
||||
|
||||
// First query is to get the most recent time the player has submitted feedback.
|
||||
var/database/query/query = new({"
|
||||
SELECT [SQLITE_FEEDBACK_COLUMN_DATETIME]
|
||||
FROM [SQLITE_TABLE_FEEDBACK]
|
||||
WHERE [SQLITE_FEEDBACK_COLUMN_AUTHOR] == ? OR [SQLITE_FEEDBACK_COLUMN_AUTHOR] == ?
|
||||
ORDER BY [SQLITE_FEEDBACK_COLUMN_DATETIME]
|
||||
DESC LIMIT 1;
|
||||
"},
|
||||
player_ckey,
|
||||
potential_hashed_ckey
|
||||
)
|
||||
query.Execute(sqlite_object)
|
||||
sqlite_check_for_errors(query, "Rate Limited Check 1")
|
||||
|
||||
// It is possible this is their first time, so there won't be a next row.
|
||||
if(query.NextRow()) // If this is true, the user has submitted feedback at least once.
|
||||
var/list/row_data = query.GetRowData()
|
||||
var/last_submission_datetime = row_data[SQLITE_FEEDBACK_COLUMN_DATETIME]
|
||||
|
||||
// Now we have the datetime, we need to do something to compare it.
|
||||
// Second query is to calculate the difference between two datetimes.
|
||||
// This is done on the SQLite side because parsing datetimes with BYOND is probably a bad idea.
|
||||
query = new(
|
||||
"SELECT julianday('now') - julianday(?) \
|
||||
AS 'datediff';",
|
||||
last_submission_datetime
|
||||
)
|
||||
query.Execute(sqlite_object)
|
||||
sqlite_check_for_errors(query, "Rate Limited Check 2")
|
||||
|
||||
query.NextRow()
|
||||
row_data = query.GetRowData()
|
||||
var/date_diff = row_data["datediff"]
|
||||
|
||||
// Now check if it's too soon to give more feedback.
|
||||
if(text2num(date_diff) < cooldown) // Too soon.
|
||||
return round(cooldown - date_diff, 0.1)
|
||||
return 0.0
|
||||
|
||||
|
||||
// A Pepper is like a Salt but only one exists and is supposed to be outside of a database.
|
||||
// If the file is properly protected, it can only be viewed/copied by sys-admins generating a log, which is much more conspicious than accessing/copying a DB.
|
||||
// This stops mods/admins/etc from guessing the author by shoving names in an MD5 hasher until they pick the right one.
|
||||
// Don't use this for things needing actual security.
|
||||
/datum/controller/subsystem/sqlite/proc/get_feedback_pepper()
|
||||
var/pepper_file = file2list("config/sqlite_feedback_pepper.txt")
|
||||
var/pepper = null
|
||||
for(var/line in pepper_file)
|
||||
if(!line)
|
||||
continue
|
||||
if(length(line) == 0)
|
||||
continue
|
||||
else if(copytext(line, 1, 2) == "#")
|
||||
continue
|
||||
else
|
||||
pepper = line
|
||||
break
|
||||
return pepper
|
||||
|
||||
/datum/controller/subsystem/sqlite/CanProcCall(procname)
|
||||
return procname != "get_feedback_pepper"
|
||||
|
||||
+398
-398
@@ -1,398 +1,398 @@
|
||||
SUBSYSTEM_DEF(vote)
|
||||
name = "Vote"
|
||||
wait = 10
|
||||
priority = FIRE_PRIORITY_VOTE
|
||||
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
|
||||
flags = SS_KEEP_TIMING | SS_NO_INIT
|
||||
var/list/round_voters = list()
|
||||
|
||||
//Current vote
|
||||
var/initiator
|
||||
var/started_time
|
||||
var/time_remaining
|
||||
var/duration
|
||||
var/mode
|
||||
var/question
|
||||
var/gamemode_vote_called = FALSE // Have we had a gamemode vote this round? Only auto-call if we haven't
|
||||
var/list/choices = list()
|
||||
var/list/gamemode_names = list()
|
||||
var/list/voted = list()
|
||||
var/list/current_votes = list()
|
||||
var/list/additional_text = list()
|
||||
|
||||
/datum/controller/subsystem/vote/fire(resumed)
|
||||
if(mode)
|
||||
time_remaining = round((started_time + duration - world.time)/10)
|
||||
if(mode == VOTE_GAMEMODE && ticker.current_state >= GAME_STATE_SETTING_UP)
|
||||
to_chat(world, "<b>Gamemode vote aborted: Game has already started.</b>")
|
||||
reset()
|
||||
return
|
||||
if(time_remaining <= 0)
|
||||
result()
|
||||
reset()
|
||||
|
||||
/datum/controller/subsystem/vote/proc/autotransfer()
|
||||
// Before doing the vote, see if anyone is playing.
|
||||
// If not, just do the transfer.
|
||||
var/players_are_in_round = FALSE
|
||||
for(var/mob/living/L as anything in player_list) // Mobs with clients attached.
|
||||
if(!istype(L)) // Exclude ghosts and other weird things.
|
||||
continue
|
||||
if(L.stat == DEAD) // Dead mobs aren't playing.
|
||||
continue
|
||||
// Everything else is, however.
|
||||
players_are_in_round = TRUE
|
||||
break
|
||||
|
||||
if(!players_are_in_round)
|
||||
log_debug("The crew transfer shuttle was automatically called at vote time due to no players being present.")
|
||||
init_shift_change(null, 1)
|
||||
return
|
||||
|
||||
initiate_vote(VOTE_CREW_TRANSFER, "the server", 1)
|
||||
log_debug("The server has called a crew transfer vote.")
|
||||
|
||||
/datum/controller/subsystem/vote/proc/autogamemode()
|
||||
initiate_vote(VOTE_GAMEMODE, "the server", 1)
|
||||
log_debug("The server has called a gamemode vote.")
|
||||
|
||||
/datum/controller/subsystem/vote/proc/reset()
|
||||
initiator = null
|
||||
started_time = null
|
||||
duration = null
|
||||
time_remaining = null
|
||||
mode = null
|
||||
question = null
|
||||
choices.Cut()
|
||||
voted.Cut()
|
||||
current_votes.Cut()
|
||||
additional_text.Cut()
|
||||
|
||||
/datum/controller/subsystem/vote/proc/get_result() // Get the highest number of votes
|
||||
var/greatest_votes = 0
|
||||
var/total_votes = 0
|
||||
|
||||
for(var/option in choices)
|
||||
var/votes = choices[option]
|
||||
total_votes += votes
|
||||
if(votes > greatest_votes)
|
||||
greatest_votes = votes
|
||||
|
||||
if(!config.vote_no_default && choices.len) // Default-vote for everyone who didn't vote
|
||||
var/non_voters = (GLOB.clients.len - total_votes)
|
||||
if(non_voters > 0)
|
||||
if(mode == VOTE_RESTART)
|
||||
choices["Continue Playing"] += non_voters
|
||||
if(choices["Continue Playing"] >= greatest_votes)
|
||||
greatest_votes = choices["Continue Playing"]
|
||||
else if(mode == VOTE_GAMEMODE)
|
||||
if(master_mode in choices)
|
||||
choices[master_mode] += non_voters
|
||||
if(choices[master_mode] >= greatest_votes)
|
||||
greatest_votes = choices[master_mode]
|
||||
else if(mode == VOTE_CREW_TRANSFER)
|
||||
var/factor = 0.5
|
||||
switch(world.time / (10 * 60)) // minutes
|
||||
if(0 to 60)
|
||||
factor = 0.5
|
||||
if(61 to 120)
|
||||
factor = 0.8
|
||||
if(121 to 240)
|
||||
factor = 1
|
||||
if(241 to 300)
|
||||
factor = 1.2
|
||||
else
|
||||
factor = 1.4
|
||||
choices["Initiate Crew Transfer"] = round(choices["Initiate Crew Transfer"] * factor)
|
||||
to_world(span_purple("Crew Transfer Factor: [factor]"))
|
||||
greatest_votes = max(choices["Initiate Crew Transfer"], choices["Extend the Shift"]) //VOREStation Edit
|
||||
|
||||
. = list() // Get all options with that many votes and return them in a list
|
||||
if(greatest_votes)
|
||||
for(var/option in choices)
|
||||
if(choices[option] == greatest_votes)
|
||||
. += option
|
||||
|
||||
/datum/controller/subsystem/vote/proc/announce_result()
|
||||
var/list/winners = get_result()
|
||||
var/text
|
||||
if(winners.len > 0)
|
||||
if(winners.len > 1)
|
||||
if(mode != VOTE_GAMEMODE || ticker.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"
|
||||
. = pick(winners)
|
||||
|
||||
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 != VOTE_GAMEMODE || . == "Extended" || ticker.hide_mode == 0) // Announce Extended gamemode, but not other gamemodes
|
||||
text += "<b>Vote Result: [mode == VOTE_GAMEMODE ? gamemode_names[.] : .]</b>"
|
||||
else
|
||||
text += "<b>The vote has ended.</b>"
|
||||
|
||||
else
|
||||
text += "<b>Vote Result: Inconclusive - No Votes!</b>"
|
||||
if(mode == VOTE_ADD_ANTAGONIST)
|
||||
antag_add_failed = 1
|
||||
log_vote(text)
|
||||
to_chat(world, span_purple("[text]"))
|
||||
|
||||
/datum/controller/subsystem/vote/proc/result()
|
||||
. = announce_result()
|
||||
var/restart = 0
|
||||
if(.)
|
||||
switch(mode)
|
||||
if(VOTE_RESTART)
|
||||
if(. == "Restart Round")
|
||||
restart = 1
|
||||
if(VOTE_GAMEMODE)
|
||||
if(master_mode != .)
|
||||
world.save_mode(.)
|
||||
if(ticker && ticker.mode)
|
||||
restart = 1
|
||||
else
|
||||
master_mode = .
|
||||
if(VOTE_CREW_TRANSFER)
|
||||
if(. == "Initiate Crew Transfer")
|
||||
init_shift_change(null, 1)
|
||||
if(VOTE_ADD_ANTAGONIST)
|
||||
if(isnull(.) || . == "None")
|
||||
antag_add_failed = 1
|
||||
else
|
||||
additional_antag_types |= antag_names_to_ids[.]
|
||||
|
||||
if(mode == VOTE_GAMEMODE) //fire this even if the vote fails.
|
||||
if(!round_progressing)
|
||||
round_progressing = 1
|
||||
to_world(span_red("<b>The round will start soon.</b>"))
|
||||
|
||||
if(restart)
|
||||
to_world("World restarting due to vote...")
|
||||
feedback_set_details("end_error", "restart vote")
|
||||
if(blackbox)
|
||||
blackbox.save_all_data_to_sql()
|
||||
sleep(50)
|
||||
log_game("Rebooting due to restart vote")
|
||||
world.Reboot()
|
||||
|
||||
/datum/controller/subsystem/vote/proc/submit_vote(ckey, newVote)
|
||||
if(mode)
|
||||
if(config.vote_no_dead && usr.stat == DEAD && !usr.client.holder)
|
||||
return
|
||||
if(current_votes[ckey])
|
||||
choices[choices[current_votes[ckey]]]--
|
||||
if(newVote && newVote >= 1 && newVote <= choices.len)
|
||||
choices[choices[newVote]]++
|
||||
current_votes[ckey] = newVote
|
||||
else
|
||||
current_votes[ckey] = null
|
||||
|
||||
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, automatic = FALSE, time = config.vote_period)
|
||||
if(!mode)
|
||||
if(started_time != null && !(check_rights(R_ADMIN|R_EVENT) || automatic))
|
||||
var/next_allowed_time = (started_time + config.vote_delay)
|
||||
if(next_allowed_time > world.time)
|
||||
return 0
|
||||
|
||||
reset()
|
||||
|
||||
switch(vote_type)
|
||||
if(VOTE_RESTART)
|
||||
choices.Add("Restart Round", "Continue Playing")
|
||||
if(VOTE_GAMEMODE)
|
||||
if(ticker.current_state >= GAME_STATE_SETTING_UP)
|
||||
return 0
|
||||
choices.Add(config.votable_modes)
|
||||
for(var/F in choices)
|
||||
var/datum/game_mode/M = gamemode_cache[F]
|
||||
if(!M)
|
||||
continue
|
||||
gamemode_names[M.config_tag] = capitalize(M.name) //It's ugly to put this here but it works
|
||||
additional_text.Add("<td align = 'center'>[M.required_players]</td>")
|
||||
gamemode_names["secret"] = "Secret"
|
||||
if(VOTE_CREW_TRANSFER)
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_EVENT, 0)) // The gods care not for the affairs of the mortals
|
||||
if(get_security_level() == "red" || get_security_level() == "delta")
|
||||
to_chat(initiator_key, "The current alert status is too high to call for a crew transfer!")
|
||||
return 0
|
||||
if(ticker.current_state <= GAME_STATE_SETTING_UP)
|
||||
to_chat(initiator_key, "The crew transfer button has been disabled!")
|
||||
return 0
|
||||
question = "Your PDA beeps with a message from Central. Would you like an additional hour to finish ongoing projects?" //VOREStation Edit
|
||||
choices.Add("Initiate Crew Transfer", "Extend the Shift") //VOREStation Edit
|
||||
if(VOTE_ADD_ANTAGONIST)
|
||||
if(!config.allow_extra_antags || ticker.current_state >= GAME_STATE_SETTING_UP)
|
||||
return 0
|
||||
for(var/antag_type in all_antag_types)
|
||||
var/datum/antagonist/antag = all_antag_types[antag_type]
|
||||
if(!(antag.id in additional_antag_types) && antag.is_votable())
|
||||
choices.Add(antag.role_text)
|
||||
choices.Add("None")
|
||||
if(VOTE_CUSTOM)
|
||||
question = sanitizeSafe(tgui_input_text(usr, "What is the vote for?"))
|
||||
if(!question)
|
||||
return 0
|
||||
for(var/i = 1 to 10)
|
||||
var/option = capitalize(sanitize(tgui_input_text(usr, "Please enter an option or hit cancel to finish")))
|
||||
if(!option || mode || !usr.client)
|
||||
break
|
||||
choices.Add(option)
|
||||
else
|
||||
return 0
|
||||
|
||||
mode = vote_type
|
||||
initiator = initiator_key
|
||||
started_time = world.time
|
||||
duration = time
|
||||
var/text = "[capitalize(mode)] vote started by [initiator]."
|
||||
if(mode == VOTE_CUSTOM)
|
||||
text += "\n[question]"
|
||||
|
||||
log_vote(text)
|
||||
|
||||
to_world(span_purple("<b>[text]</b>\nType <b>vote</b> or click <a href='?src=\ref[src]'>here</a> to place your votes.\nYou have [config.vote_period / 10] seconds to vote."))
|
||||
if(vote_type == VOTE_CREW_TRANSFER || vote_type == VOTE_GAMEMODE || vote_type == VOTE_CUSTOM)
|
||||
world << sound('sound/ambience/alarm4.ogg', repeat = 0, wait = 0, volume = 50, channel = 3)
|
||||
|
||||
if(mode == VOTE_GAMEMODE && round_progressing)
|
||||
gamemode_vote_called = TRUE
|
||||
round_progressing = 0
|
||||
to_world(span_red("<b>Round start has been delayed.</b>"))
|
||||
|
||||
time_remaining = round(config.vote_period / 10)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/datum/controller/subsystem/vote/proc/interface(var/client/C)
|
||||
if(!istype(C))
|
||||
return
|
||||
var/admin = FALSE
|
||||
if(C.holder)
|
||||
if(C.holder.rights & R_ADMIN|R_EVENT)
|
||||
admin = TRUE
|
||||
|
||||
. = "<html><head><title>Voting Panel</title></head><body>"
|
||||
if(mode)
|
||||
if(question)
|
||||
. += "<h2>Vote: '[question]'</h2>"
|
||||
else
|
||||
. += "<h2>Vote: [capitalize(mode)]</h2>"
|
||||
. += "Time Left: [time_remaining] s<hr>"
|
||||
. += "<table width = '100%'><tr><td align = 'center'><b>Choices</b></td><td align = 'center'><b>Votes</b></td>"
|
||||
if(mode == VOTE_GAMEMODE)
|
||||
.+= "<td align = 'center'><b>Minimum Players</b></td></tr>"
|
||||
|
||||
for(var/i = 1 to choices.len)
|
||||
var/votes = choices[choices[i]]
|
||||
if(!votes)
|
||||
votes = 0
|
||||
. += "<tr>"
|
||||
var/thisVote = (current_votes[C.ckey] == i)
|
||||
if(mode == VOTE_GAMEMODE)
|
||||
. += "<td>[thisVote ? "<b>" : ""]<a href='?src=\ref[src];vote=[i]'>[gamemode_names[choices[i]]]</a>[thisVote ? "</b>" : ""]</td><td align = 'center'>[votes]</td>"
|
||||
else
|
||||
. += "<td>[thisVote ? "<b>" : ""]<a href='?src=\ref[src];vote=[i]'>[choices[i]]</a>[thisVote ? "</b>" : ""]</td><td align = 'center'>[votes]</td>"
|
||||
if (additional_text.len >= i)
|
||||
. += additional_text[i]
|
||||
. += "</tr>"
|
||||
|
||||
. += "<tr><td><a href='?src=\ref[src];vote=unvote'>Unvote</a></td></tr>"
|
||||
|
||||
. += "</table><hr>"
|
||||
if(admin)
|
||||
. += "(<a href='?src=\ref[src];[HrefToken()];vote=cancel'>Cancel Vote</a>) "
|
||||
else
|
||||
. += "<h2>Start a vote:</h2><hr><ul><li>"
|
||||
if(admin || config.allow_vote_restart)
|
||||
. += "<a href='?src=\ref[src];vote=restart'>Restart</a>"
|
||||
else
|
||||
. += span_gray("Restart (Disallowed)")
|
||||
. += "</li><li>"
|
||||
|
||||
if(admin || config.allow_vote_restart)
|
||||
. += "<a href='?src=\ref[src];vote=crew_transfer'>Crew Transfer</a>"
|
||||
else
|
||||
. += span_gray("Crew Transfer (Disallowed)")
|
||||
|
||||
if(admin)
|
||||
. += "\t(<a href='?src=\ref[src];[HrefToken()];vote=toggle_restart'>[config.allow_vote_restart ? "Allowed" : "Disallowed"]</a>)"
|
||||
. += "</li><li>"
|
||||
|
||||
if(admin || config.allow_vote_mode)
|
||||
. += "<a href='?src=\ref[src];vote=gamemode'>GameMode</a>"
|
||||
else
|
||||
. += span_gray("GameMode (Disallowed)")
|
||||
|
||||
if(admin)
|
||||
. += "\t(<a href='?src=\ref[src];[HrefToken()];vote=toggle_gamemode'>[config.allow_vote_mode ? "Allowed" : "Disallowed"]</a>)"
|
||||
. += "</li><li>"
|
||||
|
||||
if(!antag_add_failed && config.allow_extra_antags)
|
||||
. += "<a href='?src=\ref[src];vote=add_antagonist'>Add Antagonist Type</a>"
|
||||
else
|
||||
. += span_gray("Add Antagonist (Disallowed)")
|
||||
. += "</li>"
|
||||
|
||||
if(admin)
|
||||
. += "<li><a href='?src=\ref[src];[HrefToken()];vote=custom'>Custom</a></li>"
|
||||
. += "</ul><hr>"
|
||||
|
||||
. += "<a href='?src=\ref[src];vote=close' style='position:absolute;right:50px'>Close</a></body></html>"
|
||||
|
||||
/datum/controller/subsystem/vote/Topic(href, href_list[])
|
||||
if(!usr || !usr.client)
|
||||
return
|
||||
switch(href_list["vote"])
|
||||
if("close")
|
||||
usr << browse(null, "window=vote")
|
||||
return
|
||||
|
||||
if("cancel")
|
||||
if(usr.client.holder)
|
||||
if("Yes" == tgui_alert(usr, "You are about to cancel this vote. Are you sure?", "Cancel Vote", list("No", "Yes")))
|
||||
reset()
|
||||
if("toggle_restart")
|
||||
if(usr.client.holder)
|
||||
config.allow_vote_restart = !config.allow_vote_restart
|
||||
if("toggle_gamemode")
|
||||
if(usr.client.holder)
|
||||
config.allow_vote_mode = !config.allow_vote_mode
|
||||
|
||||
if(VOTE_RESTART)
|
||||
if(config.allow_vote_restart || usr.client.holder)
|
||||
var/admin_number_present = send2irc_adminless_only(usr.ckey, usr)
|
||||
if(admin_number_present <= 0 || usr.client.holder)
|
||||
if(tgui_alert(usr, "Are you sure you want to start a RESTART VOTE? You should only do this if the server is dying and no staff are around to investigate.", "RESTART VOTE", list("No", "Yes I want to start a RESTART VOTE")) == "Yes I want to start a RESTART VOTE")
|
||||
initiate_vote(VOTE_RESTART, usr.key)
|
||||
else
|
||||
to_chat(usr, "<span class = 'warning'>You can't start a RESTART VOTE while there are staff around. If you are having an issue with the round, please ahelp it.</span>")
|
||||
if(VOTE_GAMEMODE)
|
||||
if(config.allow_vote_mode || usr.client.holder)
|
||||
initiate_vote(VOTE_GAMEMODE, usr.key)
|
||||
if(VOTE_CREW_TRANSFER)
|
||||
if(config.allow_vote_restart || usr.client.holder)
|
||||
initiate_vote(VOTE_CREW_TRANSFER, usr.key)
|
||||
if(VOTE_ADD_ANTAGONIST)
|
||||
if(config.allow_extra_antags || usr.client.holder)
|
||||
initiate_vote(VOTE_ADD_ANTAGONIST, usr.key)
|
||||
if(VOTE_CUSTOM)
|
||||
if(usr.client.holder)
|
||||
initiate_vote(VOTE_CUSTOM, usr.key)
|
||||
|
||||
if("unvote")
|
||||
submit_vote(usr.ckey, null)
|
||||
|
||||
else
|
||||
var/t = round(text2num(href_list["vote"]))
|
||||
if(t) // It starts from 1, so there's no problem
|
||||
submit_vote(usr.ckey, t)
|
||||
usr.client.vote()
|
||||
|
||||
/client/verb/vote()
|
||||
set category = "OOC"
|
||||
set name = "Vote"
|
||||
|
||||
if(SSvote)
|
||||
src << browse(SSvote.interface(src), "window=vote;size=500x[300 + SSvote.choices.len * 25]")
|
||||
SUBSYSTEM_DEF(vote)
|
||||
name = "Vote"
|
||||
wait = 10
|
||||
priority = FIRE_PRIORITY_VOTE
|
||||
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
|
||||
flags = SS_KEEP_TIMING | SS_NO_INIT
|
||||
var/list/round_voters = list()
|
||||
|
||||
//Current vote
|
||||
var/initiator
|
||||
var/started_time
|
||||
var/time_remaining
|
||||
var/duration
|
||||
var/mode
|
||||
var/question
|
||||
var/gamemode_vote_called = FALSE // Have we had a gamemode vote this round? Only auto-call if we haven't
|
||||
var/list/choices = list()
|
||||
var/list/gamemode_names = list()
|
||||
var/list/voted = list()
|
||||
var/list/current_votes = list()
|
||||
var/list/additional_text = list()
|
||||
|
||||
/datum/controller/subsystem/vote/fire(resumed)
|
||||
if(mode)
|
||||
time_remaining = round((started_time + duration - world.time)/10)
|
||||
if(mode == VOTE_GAMEMODE && ticker.current_state >= GAME_STATE_SETTING_UP)
|
||||
to_chat(world, "<b>Gamemode vote aborted: Game has already started.</b>")
|
||||
reset()
|
||||
return
|
||||
if(time_remaining <= 0)
|
||||
result()
|
||||
reset()
|
||||
|
||||
/datum/controller/subsystem/vote/proc/autotransfer()
|
||||
// Before doing the vote, see if anyone is playing.
|
||||
// If not, just do the transfer.
|
||||
var/players_are_in_round = FALSE
|
||||
for(var/mob/living/L as anything in player_list) // Mobs with clients attached.
|
||||
if(!istype(L)) // Exclude ghosts and other weird things.
|
||||
continue
|
||||
if(L.stat == DEAD) // Dead mobs aren't playing.
|
||||
continue
|
||||
// Everything else is, however.
|
||||
players_are_in_round = TRUE
|
||||
break
|
||||
|
||||
if(!players_are_in_round)
|
||||
log_debug("The crew transfer shuttle was automatically called at vote time due to no players being present.")
|
||||
init_shift_change(null, 1)
|
||||
return
|
||||
|
||||
initiate_vote(VOTE_CREW_TRANSFER, "the server", 1)
|
||||
log_debug("The server has called a crew transfer vote.")
|
||||
|
||||
/datum/controller/subsystem/vote/proc/autogamemode()
|
||||
initiate_vote(VOTE_GAMEMODE, "the server", 1)
|
||||
log_debug("The server has called a gamemode vote.")
|
||||
|
||||
/datum/controller/subsystem/vote/proc/reset()
|
||||
initiator = null
|
||||
started_time = null
|
||||
duration = null
|
||||
time_remaining = null
|
||||
mode = null
|
||||
question = null
|
||||
choices.Cut()
|
||||
voted.Cut()
|
||||
current_votes.Cut()
|
||||
additional_text.Cut()
|
||||
|
||||
/datum/controller/subsystem/vote/proc/get_result() // Get the highest number of votes
|
||||
var/greatest_votes = 0
|
||||
var/total_votes = 0
|
||||
|
||||
for(var/option in choices)
|
||||
var/votes = choices[option]
|
||||
total_votes += votes
|
||||
if(votes > greatest_votes)
|
||||
greatest_votes = votes
|
||||
|
||||
if(!config.vote_no_default && choices.len) // Default-vote for everyone who didn't vote
|
||||
var/non_voters = (GLOB.clients.len - total_votes)
|
||||
if(non_voters > 0)
|
||||
if(mode == VOTE_RESTART)
|
||||
choices["Continue Playing"] += non_voters
|
||||
if(choices["Continue Playing"] >= greatest_votes)
|
||||
greatest_votes = choices["Continue Playing"]
|
||||
else if(mode == VOTE_GAMEMODE)
|
||||
if(master_mode in choices)
|
||||
choices[master_mode] += non_voters
|
||||
if(choices[master_mode] >= greatest_votes)
|
||||
greatest_votes = choices[master_mode]
|
||||
else if(mode == VOTE_CREW_TRANSFER)
|
||||
var/factor = 0.5
|
||||
switch(world.time / (10 * 60)) // minutes
|
||||
if(0 to 60)
|
||||
factor = 0.5
|
||||
if(61 to 120)
|
||||
factor = 0.8
|
||||
if(121 to 240)
|
||||
factor = 1
|
||||
if(241 to 300)
|
||||
factor = 1.2
|
||||
else
|
||||
factor = 1.4
|
||||
choices["Initiate Crew Transfer"] = round(choices["Initiate Crew Transfer"] * factor)
|
||||
to_world(span_purple("Crew Transfer Factor: [factor]"))
|
||||
greatest_votes = max(choices["Initiate Crew Transfer"], choices["Extend the Shift"]) //VOREStation Edit
|
||||
|
||||
. = list() // Get all options with that many votes and return them in a list
|
||||
if(greatest_votes)
|
||||
for(var/option in choices)
|
||||
if(choices[option] == greatest_votes)
|
||||
. += option
|
||||
|
||||
/datum/controller/subsystem/vote/proc/announce_result()
|
||||
var/list/winners = get_result()
|
||||
var/text
|
||||
if(winners.len > 0)
|
||||
if(winners.len > 1)
|
||||
if(mode != VOTE_GAMEMODE || ticker.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"
|
||||
. = pick(winners)
|
||||
|
||||
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 != VOTE_GAMEMODE || . == "Extended" || ticker.hide_mode == 0) // Announce Extended gamemode, but not other gamemodes
|
||||
text += "<b>Vote Result: [mode == VOTE_GAMEMODE ? gamemode_names[.] : .]</b>"
|
||||
else
|
||||
text += "<b>The vote has ended.</b>"
|
||||
|
||||
else
|
||||
text += "<b>Vote Result: Inconclusive - No Votes!</b>"
|
||||
if(mode == VOTE_ADD_ANTAGONIST)
|
||||
antag_add_failed = 1
|
||||
log_vote(text)
|
||||
to_chat(world, span_purple("[text]"))
|
||||
|
||||
/datum/controller/subsystem/vote/proc/result()
|
||||
. = announce_result()
|
||||
var/restart = 0
|
||||
if(.)
|
||||
switch(mode)
|
||||
if(VOTE_RESTART)
|
||||
if(. == "Restart Round")
|
||||
restart = 1
|
||||
if(VOTE_GAMEMODE)
|
||||
if(master_mode != .)
|
||||
world.save_mode(.)
|
||||
if(ticker && ticker.mode)
|
||||
restart = 1
|
||||
else
|
||||
master_mode = .
|
||||
if(VOTE_CREW_TRANSFER)
|
||||
if(. == "Initiate Crew Transfer")
|
||||
init_shift_change(null, 1)
|
||||
if(VOTE_ADD_ANTAGONIST)
|
||||
if(isnull(.) || . == "None")
|
||||
antag_add_failed = 1
|
||||
else
|
||||
additional_antag_types |= antag_names_to_ids[.]
|
||||
|
||||
if(mode == VOTE_GAMEMODE) //fire this even if the vote fails.
|
||||
if(!round_progressing)
|
||||
round_progressing = 1
|
||||
to_world(span_red("<b>The round will start soon.</b>"))
|
||||
|
||||
if(restart)
|
||||
to_world("World restarting due to vote...")
|
||||
feedback_set_details("end_error", "restart vote")
|
||||
if(blackbox)
|
||||
blackbox.save_all_data_to_sql()
|
||||
sleep(50)
|
||||
log_game("Rebooting due to restart vote")
|
||||
world.Reboot()
|
||||
|
||||
/datum/controller/subsystem/vote/proc/submit_vote(ckey, newVote)
|
||||
if(mode)
|
||||
if(config.vote_no_dead && usr.stat == DEAD && !usr.client.holder)
|
||||
return
|
||||
if(current_votes[ckey])
|
||||
choices[choices[current_votes[ckey]]]--
|
||||
if(newVote && newVote >= 1 && newVote <= choices.len)
|
||||
choices[choices[newVote]]++
|
||||
current_votes[ckey] = newVote
|
||||
else
|
||||
current_votes[ckey] = null
|
||||
|
||||
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, automatic = FALSE, time = config.vote_period)
|
||||
if(!mode)
|
||||
if(started_time != null && !(check_rights(R_ADMIN|R_EVENT) || automatic))
|
||||
var/next_allowed_time = (started_time + config.vote_delay)
|
||||
if(next_allowed_time > world.time)
|
||||
return 0
|
||||
|
||||
reset()
|
||||
|
||||
switch(vote_type)
|
||||
if(VOTE_RESTART)
|
||||
choices.Add("Restart Round", "Continue Playing")
|
||||
if(VOTE_GAMEMODE)
|
||||
if(ticker.current_state >= GAME_STATE_SETTING_UP)
|
||||
return 0
|
||||
choices.Add(config.votable_modes)
|
||||
for(var/F in choices)
|
||||
var/datum/game_mode/M = gamemode_cache[F]
|
||||
if(!M)
|
||||
continue
|
||||
gamemode_names[M.config_tag] = capitalize(M.name) //It's ugly to put this here but it works
|
||||
additional_text.Add("<td align = 'center'>[M.required_players]</td>")
|
||||
gamemode_names["secret"] = "Secret"
|
||||
if(VOTE_CREW_TRANSFER)
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_EVENT, 0)) // The gods care not for the affairs of the mortals
|
||||
if(get_security_level() == "red" || get_security_level() == "delta")
|
||||
to_chat(initiator_key, "The current alert status is too high to call for a crew transfer!")
|
||||
return 0
|
||||
if(ticker.current_state <= GAME_STATE_SETTING_UP)
|
||||
to_chat(initiator_key, "The crew transfer button has been disabled!")
|
||||
return 0
|
||||
question = "Your PDA beeps with a message from Central. Would you like an additional hour to finish ongoing projects?" //VOREStation Edit
|
||||
choices.Add("Initiate Crew Transfer", "Extend the Shift") //VOREStation Edit
|
||||
if(VOTE_ADD_ANTAGONIST)
|
||||
if(!config.allow_extra_antags || ticker.current_state >= GAME_STATE_SETTING_UP)
|
||||
return 0
|
||||
for(var/antag_type in all_antag_types)
|
||||
var/datum/antagonist/antag = all_antag_types[antag_type]
|
||||
if(!(antag.id in additional_antag_types) && antag.is_votable())
|
||||
choices.Add(antag.role_text)
|
||||
choices.Add("None")
|
||||
if(VOTE_CUSTOM)
|
||||
question = sanitizeSafe(tgui_input_text(usr, "What is the vote for?"))
|
||||
if(!question)
|
||||
return 0
|
||||
for(var/i = 1 to 10)
|
||||
var/option = capitalize(sanitize(tgui_input_text(usr, "Please enter an option or hit cancel to finish")))
|
||||
if(!option || mode || !usr.client)
|
||||
break
|
||||
choices.Add(option)
|
||||
else
|
||||
return 0
|
||||
|
||||
mode = vote_type
|
||||
initiator = initiator_key
|
||||
started_time = world.time
|
||||
duration = time
|
||||
var/text = "[capitalize(mode)] vote started by [initiator]."
|
||||
if(mode == VOTE_CUSTOM)
|
||||
text += "\n[question]"
|
||||
|
||||
log_vote(text)
|
||||
|
||||
to_world(span_purple("<b>[text]</b>\nType <b>vote</b> or click <a href='?src=\ref[src]'>here</a> to place your votes.\nYou have [config.vote_period / 10] seconds to vote."))
|
||||
if(vote_type == VOTE_CREW_TRANSFER || vote_type == VOTE_GAMEMODE || vote_type == VOTE_CUSTOM)
|
||||
world << sound('sound/ambience/alarm4.ogg', repeat = 0, wait = 0, volume = 50, channel = 3)
|
||||
|
||||
if(mode == VOTE_GAMEMODE && round_progressing)
|
||||
gamemode_vote_called = TRUE
|
||||
round_progressing = 0
|
||||
to_world(span_red("<b>Round start has been delayed.</b>"))
|
||||
|
||||
time_remaining = round(config.vote_period / 10)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/datum/controller/subsystem/vote/proc/interface(var/client/C)
|
||||
if(!istype(C))
|
||||
return
|
||||
var/admin = FALSE
|
||||
if(C.holder)
|
||||
if(C.holder.rights & R_ADMIN|R_EVENT)
|
||||
admin = TRUE
|
||||
|
||||
. = "<html><head><title>Voting Panel</title></head><body>"
|
||||
if(mode)
|
||||
if(question)
|
||||
. += "<h2>Vote: '[question]'</h2>"
|
||||
else
|
||||
. += "<h2>Vote: [capitalize(mode)]</h2>"
|
||||
. += "Time Left: [time_remaining] s<hr>"
|
||||
. += "<table width = '100%'><tr><td align = 'center'><b>Choices</b></td><td align = 'center'><b>Votes</b></td>"
|
||||
if(mode == VOTE_GAMEMODE)
|
||||
.+= "<td align = 'center'><b>Minimum Players</b></td></tr>"
|
||||
|
||||
for(var/i = 1 to choices.len)
|
||||
var/votes = choices[choices[i]]
|
||||
if(!votes)
|
||||
votes = 0
|
||||
. += "<tr>"
|
||||
var/thisVote = (current_votes[C.ckey] == i)
|
||||
if(mode == VOTE_GAMEMODE)
|
||||
. += "<td>[thisVote ? "<b>" : ""]<a href='?src=\ref[src];vote=[i]'>[gamemode_names[choices[i]]]</a>[thisVote ? "</b>" : ""]</td><td align = 'center'>[votes]</td>"
|
||||
else
|
||||
. += "<td>[thisVote ? "<b>" : ""]<a href='?src=\ref[src];vote=[i]'>[choices[i]]</a>[thisVote ? "</b>" : ""]</td><td align = 'center'>[votes]</td>"
|
||||
if (additional_text.len >= i)
|
||||
. += additional_text[i]
|
||||
. += "</tr>"
|
||||
|
||||
. += "<tr><td><a href='?src=\ref[src];vote=unvote'>Unvote</a></td></tr>"
|
||||
|
||||
. += "</table><hr>"
|
||||
if(admin)
|
||||
. += "(<a href='?src=\ref[src];[HrefToken()];vote=cancel'>Cancel Vote</a>) "
|
||||
else
|
||||
. += "<h2>Start a vote:</h2><hr><ul><li>"
|
||||
if(admin || config.allow_vote_restart)
|
||||
. += "<a href='?src=\ref[src];vote=restart'>Restart</a>"
|
||||
else
|
||||
. += span_gray("Restart (Disallowed)")
|
||||
. += "</li><li>"
|
||||
|
||||
if(admin || config.allow_vote_restart)
|
||||
. += "<a href='?src=\ref[src];vote=crew_transfer'>Crew Transfer</a>"
|
||||
else
|
||||
. += span_gray("Crew Transfer (Disallowed)")
|
||||
|
||||
if(admin)
|
||||
. += "\t(<a href='?src=\ref[src];[HrefToken()];vote=toggle_restart'>[config.allow_vote_restart ? "Allowed" : "Disallowed"]</a>)"
|
||||
. += "</li><li>"
|
||||
|
||||
if(admin || config.allow_vote_mode)
|
||||
. += "<a href='?src=\ref[src];vote=gamemode'>GameMode</a>"
|
||||
else
|
||||
. += span_gray("GameMode (Disallowed)")
|
||||
|
||||
if(admin)
|
||||
. += "\t(<a href='?src=\ref[src];[HrefToken()];vote=toggle_gamemode'>[config.allow_vote_mode ? "Allowed" : "Disallowed"]</a>)"
|
||||
. += "</li><li>"
|
||||
|
||||
if(!antag_add_failed && config.allow_extra_antags)
|
||||
. += "<a href='?src=\ref[src];vote=add_antagonist'>Add Antagonist Type</a>"
|
||||
else
|
||||
. += span_gray("Add Antagonist (Disallowed)")
|
||||
. += "</li>"
|
||||
|
||||
if(admin)
|
||||
. += "<li><a href='?src=\ref[src];[HrefToken()];vote=custom'>Custom</a></li>"
|
||||
. += "</ul><hr>"
|
||||
|
||||
. += "<a href='?src=\ref[src];vote=close' style='position:absolute;right:50px'>Close</a></body></html>"
|
||||
|
||||
/datum/controller/subsystem/vote/Topic(href, href_list[])
|
||||
if(!usr || !usr.client)
|
||||
return
|
||||
switch(href_list["vote"])
|
||||
if("close")
|
||||
usr << browse(null, "window=vote")
|
||||
return
|
||||
|
||||
if("cancel")
|
||||
if(usr.client.holder)
|
||||
if("Yes" == tgui_alert(usr, "You are about to cancel this vote. Are you sure?", "Cancel Vote", list("No", "Yes")))
|
||||
reset()
|
||||
if("toggle_restart")
|
||||
if(usr.client.holder)
|
||||
config.allow_vote_restart = !config.allow_vote_restart
|
||||
if("toggle_gamemode")
|
||||
if(usr.client.holder)
|
||||
config.allow_vote_mode = !config.allow_vote_mode
|
||||
|
||||
if(VOTE_RESTART)
|
||||
if(config.allow_vote_restart || usr.client.holder)
|
||||
var/admin_number_present = send2irc_adminless_only(usr.ckey, usr)
|
||||
if(admin_number_present <= 0 || usr.client.holder)
|
||||
if(tgui_alert(usr, "Are you sure you want to start a RESTART VOTE? You should only do this if the server is dying and no staff are around to investigate.", "RESTART VOTE", list("No", "Yes I want to start a RESTART VOTE")) == "Yes I want to start a RESTART VOTE")
|
||||
initiate_vote(VOTE_RESTART, usr.key)
|
||||
else
|
||||
to_chat(usr, "<span class = 'warning'>You can't start a RESTART VOTE while there are staff around. If you are having an issue with the round, please ahelp it.</span>")
|
||||
if(VOTE_GAMEMODE)
|
||||
if(config.allow_vote_mode || usr.client.holder)
|
||||
initiate_vote(VOTE_GAMEMODE, usr.key)
|
||||
if(VOTE_CREW_TRANSFER)
|
||||
if(config.allow_vote_restart || usr.client.holder)
|
||||
initiate_vote(VOTE_CREW_TRANSFER, usr.key)
|
||||
if(VOTE_ADD_ANTAGONIST)
|
||||
if(config.allow_extra_antags || usr.client.holder)
|
||||
initiate_vote(VOTE_ADD_ANTAGONIST, usr.key)
|
||||
if(VOTE_CUSTOM)
|
||||
if(usr.client.holder)
|
||||
initiate_vote(VOTE_CUSTOM, usr.key)
|
||||
|
||||
if("unvote")
|
||||
submit_vote(usr.ckey, null)
|
||||
|
||||
else
|
||||
var/t = round(text2num(href_list["vote"]))
|
||||
if(t) // It starts from 1, so there's no problem
|
||||
submit_vote(usr.ckey, t)
|
||||
usr.client.vote()
|
||||
|
||||
/client/verb/vote()
|
||||
set category = "OOC"
|
||||
set name = "Vote"
|
||||
|
||||
if(SSvote)
|
||||
src << browse(SSvote.interface(src), "window=vote;size=500x[300 + SSvote.choices.len * 25]")
|
||||
|
||||
+109
-109
@@ -1,109 +1,109 @@
|
||||
//TODO: rewrite and standardise all controller datums to the datum/controller type
|
||||
//TODO: allow all controllers to be deleted for clean restarts (see WIP master controller stuff) - MC done - lighting done
|
||||
|
||||
// Clickable stat() button.
|
||||
/obj/effect/statclick
|
||||
name = "Initializing..."
|
||||
blocks_emissive = FALSE
|
||||
var/target
|
||||
|
||||
/obj/effect/statclick/New(loc, text, target) //Don't port this to Initialize it's too critical
|
||||
..()
|
||||
name = text
|
||||
src.target = target
|
||||
|
||||
/obj/effect/statclick/proc/update(text)
|
||||
name = text
|
||||
return src
|
||||
|
||||
/obj/effect/statclick/debug
|
||||
var/class
|
||||
|
||||
/obj/effect/statclick/debug/Click()
|
||||
if(!usr.client.holder || !target)
|
||||
return
|
||||
if(!class)
|
||||
if(istype(target, /datum/controller/subsystem))
|
||||
class = "subsystem"
|
||||
else if(istype(target, /datum/controller))
|
||||
class = "controller"
|
||||
else if(istype(target, /datum))
|
||||
class = "datum"
|
||||
else
|
||||
class = "unknown"
|
||||
|
||||
usr.client.debug_variables(target)
|
||||
message_admins("Admin [key_name_admin(usr)] is debugging the [target] [class].")
|
||||
|
||||
|
||||
// Debug verbs.
|
||||
/client/proc/restart_controller(controller in list("Master", "Failsafe"))
|
||||
set category = "Debug"
|
||||
set name = "Restart Controller"
|
||||
set desc = "Restart one of the various periodic loop controllers for the game (be careful!)"
|
||||
|
||||
if(!holder)
|
||||
return
|
||||
switch(controller)
|
||||
if("Master")
|
||||
Recreate_MC()
|
||||
feedback_add_details("admin_verb","RMC")
|
||||
if("Failsafe")
|
||||
new /datum/controller/failsafe()
|
||||
feedback_add_details("admin_verb","RFailsafe")
|
||||
|
||||
message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
|
||||
|
||||
/client/proc/debug_antagonist_template(antag_type in all_antag_types)
|
||||
set category = "Debug"
|
||||
set name = "Debug Antagonist"
|
||||
set desc = "Debug an antagonist template."
|
||||
|
||||
var/datum/antagonist/antag = all_antag_types[antag_type]
|
||||
if(antag)
|
||||
usr.client.debug_variables(antag)
|
||||
message_admins("Admin [key_name_admin(usr)] is debugging the [antag.role_text] template.")
|
||||
|
||||
/client/proc/debug_controller()
|
||||
set category = "Debug"
|
||||
set name = "Debug Controller"
|
||||
set desc = "Debug the various subsystems/controllers for the game (be careful!)"
|
||||
|
||||
if(!holder)
|
||||
return
|
||||
var/list/options = list()
|
||||
options["MC"] = Master
|
||||
options["Failsafe"] = Failsafe
|
||||
options["Configuration"] = config
|
||||
for(var/datum/controller/subsystem/S as anything in Master.subsystems)
|
||||
if(!istype(S)) //Eh, we're a debug verb, let's have typechecking.
|
||||
continue
|
||||
var/strtype = "SS[get_end_section_of_type(S.type)]"
|
||||
if(options[strtype])
|
||||
var/offset = 2
|
||||
while(istype(options["[strtype]_[offset] - DUPE ERROR"], /datum/controller/subsystem))
|
||||
offset++
|
||||
options["[strtype]_[offset] - DUPE ERROR"] = S //Something is very, very wrong.
|
||||
else
|
||||
options[strtype] = S
|
||||
|
||||
//Goon PS stuff, and other yet-to-be-subsystem things.
|
||||
options["LEGACY: master_controller"] = master_controller
|
||||
options["LEGACY: air_master"] = air_master
|
||||
options["LEGACY: job_master"] = job_master
|
||||
options["LEGACY: radio_controller"] = radio_controller
|
||||
options["LEGACY: emergency_shuttle"] = emergency_shuttle
|
||||
options["LEGACY: paiController"] = paiController
|
||||
options["LEGACY: cameranet"] = cameranet
|
||||
options["LEGACY: transfer_controller"] = transfer_controller
|
||||
options["LEGACY: gas_data"] = gas_data
|
||||
|
||||
var/pick = input(mob, "Choose a controller to debug/view variables of.", "VV controller:") as null|anything in options // Leaving as input() due to debug tool
|
||||
if(!pick)
|
||||
return
|
||||
var/datum/D = options[pick]
|
||||
if(!istype(D))
|
||||
return
|
||||
feedback_add_details("admin_verb", "DebugController")
|
||||
message_admins("Admin [key_name_admin(mob)] is debugging the [pick] controller.")
|
||||
debug_variables(D)
|
||||
//TODO: rewrite and standardise all controller datums to the datum/controller type
|
||||
//TODO: allow all controllers to be deleted for clean restarts (see WIP master controller stuff) - MC done - lighting done
|
||||
|
||||
// Clickable stat() button.
|
||||
/obj/effect/statclick
|
||||
name = "Initializing..."
|
||||
blocks_emissive = FALSE
|
||||
var/target
|
||||
|
||||
/obj/effect/statclick/New(loc, text, target) //Don't port this to Initialize it's too critical
|
||||
..()
|
||||
name = text
|
||||
src.target = target
|
||||
|
||||
/obj/effect/statclick/proc/update(text)
|
||||
name = text
|
||||
return src
|
||||
|
||||
/obj/effect/statclick/debug
|
||||
var/class
|
||||
|
||||
/obj/effect/statclick/debug/Click()
|
||||
if(!usr.client.holder || !target)
|
||||
return
|
||||
if(!class)
|
||||
if(istype(target, /datum/controller/subsystem))
|
||||
class = "subsystem"
|
||||
else if(istype(target, /datum/controller))
|
||||
class = "controller"
|
||||
else if(istype(target, /datum))
|
||||
class = "datum"
|
||||
else
|
||||
class = "unknown"
|
||||
|
||||
usr.client.debug_variables(target)
|
||||
message_admins("Admin [key_name_admin(usr)] is debugging the [target] [class].")
|
||||
|
||||
|
||||
// Debug verbs.
|
||||
/client/proc/restart_controller(controller in list("Master", "Failsafe"))
|
||||
set category = "Debug"
|
||||
set name = "Restart Controller"
|
||||
set desc = "Restart one of the various periodic loop controllers for the game (be careful!)"
|
||||
|
||||
if(!holder)
|
||||
return
|
||||
switch(controller)
|
||||
if("Master")
|
||||
Recreate_MC()
|
||||
feedback_add_details("admin_verb","RMC")
|
||||
if("Failsafe")
|
||||
new /datum/controller/failsafe()
|
||||
feedback_add_details("admin_verb","RFailsafe")
|
||||
|
||||
message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
|
||||
|
||||
/client/proc/debug_antagonist_template(antag_type in all_antag_types)
|
||||
set category = "Debug"
|
||||
set name = "Debug Antagonist"
|
||||
set desc = "Debug an antagonist template."
|
||||
|
||||
var/datum/antagonist/antag = all_antag_types[antag_type]
|
||||
if(antag)
|
||||
usr.client.debug_variables(antag)
|
||||
message_admins("Admin [key_name_admin(usr)] is debugging the [antag.role_text] template.")
|
||||
|
||||
/client/proc/debug_controller()
|
||||
set category = "Debug"
|
||||
set name = "Debug Controller"
|
||||
set desc = "Debug the various subsystems/controllers for the game (be careful!)"
|
||||
|
||||
if(!holder)
|
||||
return
|
||||
var/list/options = list()
|
||||
options["MC"] = Master
|
||||
options["Failsafe"] = Failsafe
|
||||
options["Configuration"] = config
|
||||
for(var/datum/controller/subsystem/S as anything in Master.subsystems)
|
||||
if(!istype(S)) //Eh, we're a debug verb, let's have typechecking.
|
||||
continue
|
||||
var/strtype = "SS[get_end_section_of_type(S.type)]"
|
||||
if(options[strtype])
|
||||
var/offset = 2
|
||||
while(istype(options["[strtype]_[offset] - DUPE ERROR"], /datum/controller/subsystem))
|
||||
offset++
|
||||
options["[strtype]_[offset] - DUPE ERROR"] = S //Something is very, very wrong.
|
||||
else
|
||||
options[strtype] = S
|
||||
|
||||
//Goon PS stuff, and other yet-to-be-subsystem things.
|
||||
options["LEGACY: master_controller"] = master_controller
|
||||
options["LEGACY: air_master"] = air_master
|
||||
options["LEGACY: job_master"] = job_master
|
||||
options["LEGACY: radio_controller"] = radio_controller
|
||||
options["LEGACY: emergency_shuttle"] = emergency_shuttle
|
||||
options["LEGACY: paiController"] = paiController
|
||||
options["LEGACY: cameranet"] = cameranet
|
||||
options["LEGACY: transfer_controller"] = transfer_controller
|
||||
options["LEGACY: gas_data"] = gas_data
|
||||
|
||||
var/pick = input(mob, "Choose a controller to debug/view variables of.", "VV controller:") as null|anything in options // Leaving as input() due to debug tool
|
||||
if(!pick)
|
||||
return
|
||||
var/datum/D = options[pick]
|
||||
if(!istype(D))
|
||||
return
|
||||
feedback_add_details("admin_verb", "DebugController")
|
||||
message_admins("Admin [key_name_admin(mob)] is debugging the [pick] controller.")
|
||||
debug_variables(D)
|
||||
|
||||
Reference in New Issue
Block a user