Skip to content

Commit b531456

Browse files
NonQueueingMattMatt Atlasalsoandanswer
authored
Adds Round Canonicity to the game, and makes Storytellers able to edit round canonicity. (#22775)
To clear up when a round is canon, what type of canonicity it is and what exactly it entails, canonicity information has been added to the game. Through the Canon Panel, players can now see what exactly the current round's rules on character deaths, antags, and character interactions are. Additionally, this lets developers actually know _when_ exactly a round is canon, which will be very useful for persistence. Storytellers and admins can change the canonicity of the round in the panel as well. Some examples: <img width="400" height="600" alt="image" src="https://github.com/user-attachments/assets/e71d2b18-0741-4bc0-aa98-979bb696d8a6" /> <img width="400" height="600" alt="image" src="https://github.com/user-attachments/assets/29486039-7625-4443-af2e-3ac982727e5c" /> <img width="400" height="600" alt="image" src="https://github.com/user-attachments/assets/7522f5f0-4d4e-4bae-906e-2b07067af3a5" /> --------- Signed-off-by: Matt Atlas <mattiathebest2000@hotmail.it> Co-authored-by: Matt Atlas <liermattia@gmail.com> Co-authored-by: Wowzewow (Wezzy) <42310821+alsoandanswer@users.noreply.github.com>
1 parent 92e4ea7 commit b531456

18 files changed

Lines changed: 437 additions & 44 deletions

File tree

aurorastation.dme

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "code\__DEFINES\battle_monsters.dm"
4444
#include "code\__DEFINES\byond_compat.dm"
4545
#include "code\__DEFINES\callbacks.dm"
46+
#include "code\__DEFINES\canonicity.dm"
4647
#include "code\__DEFINES\chemistry.dm"
4748
#include "code\__DEFINES\circuitboard.dm"
4849
#include "code\__DEFINES\color.dm"
@@ -134,7 +135,6 @@
134135
#include "code\__DEFINES\rust_bapi.dm"
135136
#include "code\__DEFINES\rust_g.dm"
136137
#include "code\__DEFINES\rust_g_overrides.dm"
137-
#include "code\__DEFINES\scenarios.dm"
138138
#include "code\__DEFINES\ship_locations.dm"
139139
#include "code\__DEFINES\ship_weapons.dm"
140140
#include "code\__DEFINES\shuttle.dm"
@@ -482,6 +482,7 @@
482482
#include "code\datums\progressbar.dm"
483483
#include "code\datums\records.dm"
484484
#include "code\datums\room_template.dm"
485+
#include "code\datums\round_canonicity.dm"
485486
#include "code\datums\ruins.dm"
486487
#include "code\datums\signals.dm"
487488
#include "code\datums\sound_player.dm"

code/__DEFINES/canonicity.dm

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/// Nothing in the round is canon; such as in a non-canon event. Things are forgotten the next round.
2+
#define ROUND_NON_CANON 0
3+
/// Actions taken during this round are canon. Antagonist actions are not considered here; see below.
4+
#define ROUND_FULL_CANON 1
5+
6+
/// Antagonist actions are not expected during this round.
7+
#define ANTAGONIST_ACTIONS_NOT_EXPECTED 0
8+
/// Antagonist actions are NOT canon during this round.
9+
#define ANTAGONIST_ACTIONS_NOT_CANON 1
10+
/// Antagonist actions are canon during this round.
11+
#define ANTAGONIST_ACTIONS_CANON 2
12+
13+
14+
/// Character deaths are automatically not-canon. Usually the case in non-canon events.
15+
#define NO_CHARACTER_DEATH 0
16+
/// If character deaths are limited, and thus can be retconned if all player parties agree.
17+
#define LIMITED_CHARACTER_DEATH 1
18+
/// If players are FORCED to keep character deaths canon. In this case, ALL CHARACTER DEATHS MUST GO THROUGH HEADMINS AND LOREMASTERS TO BE RETCONNED. THERE ARE NO EXCEPTIONS!
19+
#define FORCED_CHARACTER_DEATH 2
20+
21+
/// Away sites are NOT canon in this round.
22+
#define AWAY_SITE_NOT_CANON 0
23+
/// It's canon that you went to an away site, but the exact details are not (to prevent everyone from knowing the exact layout of the propellant station or whatever)
24+
#define AWAY_SITE_CANON_LIMITED 1
25+
/// The away site in its entirety is canon.
26+
#define AWAY_SITE_CANON_FULL 2
27+
28+
/// Offships are not canon at all.
29+
#define OFFSHIP_NOT_CANON 0
30+
/// Offship actions are canon, barring extreme actions like bombing the main ship or whatever.
31+
#define OFFSHIP_CANON_LIMITED 1
32+
/// Offship actions are fully canon.
33+
#define OFFSHIP_CANON_FULL 2

code/__DEFINES/scenarios.dm

Lines changed: 0 additions & 4 deletions
This file was deleted.

code/controllers/subsystems/processing/odyssey.dm

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ SUBSYSTEM_DEF(odyssey)
7979
setup_scenario_variables()
8080
var/list/possible_station_levels = SSmapping.levels_by_all_traits(list(ZTRAIT_STATION))
8181
main_map = GLOB.map_sectors["[pick(possible_station_levels)]"]
82+
if(!SSticker.round_canon_admin_forced)
83+
SSticker.set_round_canon(scenario.scenario_canonicity_type, TRUE)
8284

8385
// Now that we actually have an odyssey, the subsystem can fire!
8486
can_fire = TRUE
@@ -95,13 +97,13 @@ SUBSYSTEM_DEF(odyssey)
9597
ody_gamemode.required_players = scenario.min_player_amount
9698
ody_gamemode.required_enemies = scenario.min_actor_amount
9799

98-
//Setting the scenario_type variable for use here in UI info and chat notices.
99-
if(!length(scenario.possible_scenario_types))
100-
scenario.scenario_type = SCENARIO_TYPE_NONCANON
100+
//Setting the scenario_canonicity_type variable for use here in UI info and chat notices.
101+
if(!length(scenario.possible_scenario_canonicity_types))
102+
scenario.scenario_canonicity_type = /singleton/canonicity/limited
101103
else if(SSatlas.current_sector in ALL_EVENT_ONLY_SECTORS) // If we are in an exclusive event area for an arc (EG. The Horizon finds itself isolated and alone), we may not want canon odysseys spawning.
102-
scenario.scenario_type = SCENARIO_TYPE_NONCANON // Noncanon odysseys are fine though!
104+
scenario.scenario_canonicity_type = /singleton/canonicity/limited // Noncanon odysseys are fine though!
103105
else
104-
scenario.scenario_type = pick(scenario.possible_scenario_types)
106+
scenario.scenario_canonicity_type = pick(scenario.possible_scenario_canonicity_types)
105107

106108
site_landing_restricted = scenario.site_landing_restricted
107109

@@ -153,7 +155,7 @@ SUBSYSTEM_DEF(odyssey)
153155
if(scenario)
154156
data["scenario_name"] = SSodyssey.scenario.name
155157
data["scenario_desc"] = SSodyssey.scenario.desc
156-
data["scenario_canonicity"] = SSodyssey.scenario.scenario_type == SCENARIO_TYPE_CANON ? "Canon" : "Non-Canon"
158+
data["scenario_canonicity"] = SSticker.round_canon.name
157159
data["is_storyteller"] = isstoryteller(user) || check_rights(R_ADMIN, FALSE, user)
158160

159161
if(length(scenario.roles))

code/controllers/subsystems/statpanel.dm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ SUBSYSTEM_DEF(statpanels)
3636
"Last Transfer Vote: [GLOB.last_transfer_vote ? time2text(GLOB.last_transfer_vote, "hh:mm") : "Never"]",
3737
"Next Port Visit: [SSatlas.current_sector.next_port_visit_string]"
3838
)
39+
40+
if(istype(SSticker.round_canon))
41+
global_data[++global_data.len] = list(
42+
"Round Canon: ",
43+
"[SSticker.round_canon.name]",
44+
"src=[REF(src)];open_canon_panel=1"
45+
)
46+
3947
if(eta_status)
4048
global_data += eta_status
4149

@@ -100,6 +108,10 @@ SUBSYSTEM_DEF(statpanels)
100108
if(MC_TICK_CHECK)
101109
return
102110

111+
/datum/controller/subsystem/statpanels/Topic(href, href_list)
112+
if(href_list["open_canon_panel"])
113+
SSticker.round_canon.ui_interact(usr)
114+
103115
/datum/controller/subsystem/statpanels/proc/set_status_tab(client/target)
104116
if(!global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data()
105117
var/list/preliminary_stats = list("The server is initializing...")

code/controllers/subsystems/ticker.dm

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ SUBSYSTEM_DEF(ticker)
7171
var/total_players_ready = 0
7272
var/list/ready_player_jobs
7373

74+
/// The round canonicity. Set by either admins or by the gamemode.
75+
var/singleton/canonicity/round_canon
76+
/// Round canon forced by admins in the lobby; prevents gamemodes from overriding.
77+
var/round_canon_admin_forced = FALSE
78+
7479
/datum/controller/subsystem/ticker/Initialize(timeofday)
7580
pregame()
7681
restart_timeout = GLOB.config.restart_timeout
@@ -808,6 +813,18 @@ SUBSYSTEM_DEF(ticker)
808813
return TRUE
809814
. = ..()
810815

816+
/datum/controller/subsystem/ticker/proc/set_round_canon(canon_type, pre_game = FALSE, announce = FALSE)
817+
round_canon = GET_SINGLETON(canon_type)
818+
if(!istype(round_canon))
819+
round_canon = GET_SINGLETON(/singleton/canonicity/limited)
820+
821+
if(pre_game)
822+
round_canon.pre_game_setup()
823+
824+
if(announce)
825+
var/announcement = SPAN_NOTICE("The round canonicity has been set to [SPAN_DANGER(round_canon.name)].<br> For more information, press the [round_canon.name] button in your Status panel.")
826+
to_world(EXAMINE_BLOCK_ODYSSEY(FONT_LARGE(announcement)))
827+
811828
#undef SETUP_OK
812829
#undef SETUP_REVOTE
813830
#undef SETUP_REATTEMPT

code/datums/round_canonicity.dm

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/singleton/canonicity
2+
/// This is the name of the canonicity type.
3+
var/name = "Round Canonicity"
4+
/// The description. Should fill in the users about the details of the round canonicity.
5+
var/desc = "This is a base type, report this to a dev if it somehow happens to be used."
6+
/// If the round itself is canon or not. Non-canon rounds are things like non-canon events, where everything is "forgotten" the next round.
7+
var/round_canon
8+
/// If antagonist actions are canon during this round.
9+
var/antagonist_actions_canon
10+
/// If character deaths are canon during this round.
11+
var/character_death_canon
12+
/// If away sites are canon or not. It's a bit more complex than just that; see the relevant defines.
13+
var/away_site_canon
14+
/// If offships are canon or not. It's a bit more complex than just that; see the relevant defines.
15+
var/offship_canon
16+
17+
/**
18+
* This proc is automatically called on /datum/game_mode/proc/pre_game_setup().
19+
* See that proc for details.
20+
*/
21+
/singleton/canonicity/proc/pre_game_setup()
22+
return
23+
24+
/**
25+
* Called when someone clicks on the stat panel entry to open the canon panel TGUI.
26+
*/
27+
/singleton/canonicity/ui_interact(mob/user, datum/tgui/ui)
28+
ui = SStgui.try_update_ui(user, src, ui)
29+
if(!ui)
30+
ui = new(user, src, "CanonPanel", "Canon Panel")
31+
ui.open()
32+
33+
/singleton/canonicity/ui_state(mob/user)
34+
return GLOB.always_state
35+
36+
/singleton/canonicity/ui_status(mob/user, datum/ui_state/state)
37+
return UI_INTERACTIVE
38+
39+
/singleton/canonicity/ui_static_data(mob/user)
40+
var/list/data = list()
41+
42+
data["name"] = name
43+
data["desc"] = desc
44+
45+
data["round_canon_info"] = round_canon_info()
46+
data["antagonist_actions_canon_info"] = antagonist_actions_canon_info()
47+
data["character_death_canon_info"] = character_death_canon_info()
48+
data["away_site_canon_info"] = away_site_canon_info()
49+
data["offship_canon_info"] = offship_canon_info()
50+
51+
data["is_storyteller"] = isstoryteller(user)
52+
data["is_admin"] = check_rights(R_ADMIN, FALSE, user)
53+
54+
return data
55+
56+
/singleton/canonicity/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
57+
. = ..()
58+
switch(action)
59+
if("edit_round_canon_type")
60+
var/singleton/canonicity/canon_types = GET_SINGLETON_SUBTYPE_LIST(/singleton/canonicity)
61+
var/singleton/canonicity/selected_canon = tgui_input_list(ui.user, "Select a new canonicity for this round.", "Canon Panel", canon_types)
62+
if(istype(selected_canon))
63+
var/changer = ui.user
64+
SSticker.set_round_canon(selected_canon.type, FALSE, TRUE)
65+
SStgui.close_all_uis(src)
66+
message_admins(SPAN_DANGER("[changer] has changed the round canonicity to [SSticker.round_canon.name]."))
67+
68+
/singleton/canonicity/proc/round_canon_info()
69+
. = list()
70+
switch(round_canon)
71+
if(ROUND_FULL_CANON)
72+
. += "This round is canon. All actions taken by non-antagonists during this round are considered canon."
73+
if(ROUND_NON_CANON)
74+
. += "This round is non-canon. All actions taken during this round are considered non-canon."
75+
76+
/singleton/canonicity/proc/antagonist_actions_canon_info()
77+
. = list()
78+
switch(antagonist_actions_canon)
79+
if(ANTAGONIST_ACTIONS_NOT_EXPECTED)
80+
. += "Antagonist actions are not expected during this round."
81+
if(ANTAGONIST_ACTIONS_CANON)
82+
. += "Antagonist actions are canon during this round. This includes event antagonists."
83+
if(ANTAGONIST_ACTIONS_NOT_CANON)
84+
. += "Antagonist actions are not canon during this round."
85+
. += "This includes actions by non-antagonists directly influenced by antagonists."
86+
87+
/singleton/canonicity/proc/character_death_canon_info()
88+
. = list()
89+
switch(character_death_canon)
90+
if(LIMITED_CHARACTER_DEATH)
91+
. += "Character deaths are limited during this round. If all involved parties agree, character deaths can be retconned. If you would like to contest a death, or determine if a party counts as involved, please adminhelp."
92+
. += "To count as involved, a player has to be an active participant of your death, meaning that they must have intentionally contributed to it. As an example, if you die to a carp on an expedition, you can retcon this death without asking others."
93+
if(FORCED_CHARACTER_DEATH)
94+
. += "Character deaths are forced canon during this round. All character deaths must go through headmins and loremasters to be retconned, no exceptions!"
95+
if(NO_CHARACTER_DEATH)
96+
. += "Character deaths are not canon during this round."
97+
98+
/singleton/canonicity/proc/away_site_canon_info()
99+
. = list()
100+
switch(away_site_canon)
101+
if(AWAY_SITE_NOT_CANON)
102+
. += "Away sites are not canon this round."
103+
if(AWAY_SITE_CANON_LIMITED)
104+
. += "Away site canon is limited. This means that while it is canon that you go to an away site, the exact details of where you went are not. You can say that you went to a greimorian infested station, for example, \
105+
but you cannot remember the same away site across rounds."
106+
if(AWAY_SITE_CANON_FULL)
107+
. += "Away sites are canon, including the exact details."
108+
109+
/singleton/canonicity/proc/offship_canon_info()
110+
. = list()
111+
switch(offship_canon)
112+
if(OFFSHIP_NOT_CANON)
113+
. += "Offship actions are not canon during this round."
114+
if(OFFSHIP_CANON_LIMITED)
115+
. += "Offship action canon is limited. This means that offship actions are canon, barring hostile actions taken against the [SSatlas.current_map.full_name]. If anything is unclear, make sure to adminhelp."
116+
if(OFFSHIP_CANON_FULL)
117+
. += "Offship actions are canon during this round."
118+
119+
/singleton/canonicity/extended
120+
name = "Extended Canon"
121+
desc = "This type of canonicity is in place for Extended rounds or canon events."
122+
round_canon = ROUND_FULL_CANON
123+
antagonist_actions_canon = ANTAGONIST_ACTIONS_NOT_EXPECTED
124+
character_death_canon = LIMITED_CHARACTER_DEATH
125+
away_site_canon = AWAY_SITE_CANON_LIMITED
126+
offship_canon = OFFSHIP_CANON_LIMITED
127+
128+
/singleton/canonicity/odyssey
129+
name = "Odyssey Canon"
130+
desc = "This type of canonicity is in place for Odyssey rounds, where antagonists are present and their actions are considered canon."
131+
round_canon = ROUND_FULL_CANON
132+
antagonist_actions_canon = ANTAGONIST_ACTIONS_CANON
133+
character_death_canon = LIMITED_CHARACTER_DEATH
134+
away_site_canon = AWAY_SITE_CANON_LIMITED
135+
offship_canon = OFFSHIP_CANON_LIMITED
136+
137+
/singleton/canonicity/limited
138+
name = "Limited Canon"
139+
desc = "This type of canonicity is in place for Secret rounds or Odysseys where the antagonist actions are not considered canon."
140+
round_canon = ROUND_FULL_CANON
141+
antagonist_actions_canon = ANTAGONIST_ACTIONS_NOT_CANON
142+
character_death_canon = LIMITED_CHARACTER_DEATH
143+
away_site_canon = AWAY_SITE_CANON_LIMITED
144+
offship_canon = OFFSHIP_CANON_LIMITED
145+
146+
/singleton/canonicity/canon_event
147+
name = "Full Canon"
148+
desc = "This type of canonicity is in place for canon event rounds."
149+
round_canon = ROUND_FULL_CANON
150+
antagonist_actions_canon = ANTAGONIST_ACTIONS_CANON
151+
character_death_canon = FORCED_CHARACTER_DEATH
152+
away_site_canon = AWAY_SITE_CANON_FULL
153+
offship_canon = OFFSHIP_CANON_FULL
154+
155+
/singleton/canonicity/non_canon_event
156+
name = "Non-Canon Event"
157+
desc = "This type of canonicity is in place for non-canon events."
158+
round_canon = ROUND_NON_CANON
159+
antagonist_actions_canon = ANTAGONIST_ACTIONS_NOT_CANON
160+
character_death_canon = NO_CHARACTER_DEATH
161+
away_site_canon = AWAY_SITE_NOT_CANON
162+
offship_canon = OFFSHIP_NOT_CANON

code/datums/scenarios/scenario.dm

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
var/desc = "A generic scenario that should not be in the rotation."
66
/// What sectors this scenario can spawn in. An empty list is all sectors.
77
var/list/sector_whitelist = list()
8-
/// The type of scenario this is. NOT a boolean or a bitfield. This is automatically set on /datum/controller/subsystem/odyssey/proc/setup_scenario_variables().
9-
var/scenario_type
10-
/// A list containing the options for type of scenario. An empty list will default to SCENARIO_TYPE_NONCANON.
11-
var/list/possible_scenario_types = list()
8+
/// The canonicity of the scenario. Subtype of /singleton/canonicity. This is automatically set on /datum/controller/subsystem/odyssey/proc/setup_scenario_variables().
9+
var/scenario_canonicity_type
10+
/// A list containing the options for type of scenario. An empty list will default to /singleton/canonicity/limited.
11+
var/list/possible_scenario_canonicity_types = list()
1212
/// Whether or not landing on the Odyssey away site is restricted by default. Has to be either unrestricted by Storytellers manually, or happens forcefully at 40 minutes in.
1313
var/site_landing_restricted = TRUE
1414

code/game/gamemodes/extended/extended.dm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
required_players = 0
55
round_description = "Just have fun and role-play!"
66
extended_round_description = "There are no antagonists during extended, unless an admin decides to be cheeky. Just play your character, mess around with your job, and have fun."
7+
canon_type = /singleton/canonicity/extended

code/game/gamemodes/game_mode.dm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,18 @@ GLOBAL_LIST_EMPTY(additional_antag_types)
3434
var/event_delay_mod_moderate // Modifies the timing of random events.
3535
var/event_delay_mod_major // As above.
3636

37+
/// The canon type of this gamemode. THIS SHOULD ALWAYS BE SET.
38+
var/canon_type = /singleton/canonicity/limited
39+
3740
/datum/game_mode/New()
3841
..()
3942
// Enforce some formatting.
4043
// This will probably break something.
4144
name = capitalize(lowertext(name))
4245
config_tag = lowertext(config_tag)
46+
if(!canon_type)
47+
message_admins("Gamemode [name] has no canon_type set! Defaulting to limited canon.")
48+
canon_type = /singleton/canonicity/limited
4349

4450
/datum/game_mode/Topic(href, href_list[])
4551
if(..())
@@ -498,6 +504,8 @@ GLOBAL_LIST_EMPTY(additional_antag_types)
498504
* This proc should always return TRUE on a success and FALSE if something went wrong, so that if the initialization failed, the game can reset to lobby state.
499505
*/
500506
/datum/game_mode/proc/pre_game_setup()
507+
if(!SSticker.round_canon_admin_forced)
508+
SSticker.set_round_canon(canon_type, TRUE)
501509
return TRUE
502510

503511
//////////////////////////

0 commit comments

Comments
 (0)