mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-09 16:05:07 +00:00
[READY FOR MERGE] Storyteller 2.2 (#2116)
## About The Pull Request This one is bound to come with mostly admin and code facing changes, players are bound to expect one less ms of runtime and ~~maybe a different antag cap system~~ (Later). ### Storyteller admin UI Creates a fresh new ui for the storyteller admin side, making it significantly easier to use than before, and adds some neat elements that visualize what's going on.  ## Why It's Good For The Game The code sucks less, the ui sucks less ## Proof Of Testing The TM has not been met with any major issues from the admins. ## Changelog 🆑 admin: New storyteller admin panel admin: More logging to the storyteller panel actions and to it spawning things /🆑 --------- Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com> Co-authored-by: The Sharkening <95130227+StrangeWeirdKitten@users.noreply.github.com> Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
This commit is contained in:
@@ -114,7 +114,7 @@
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
//SSdynamic.admin_panel() // BUBBER EDIT - STORYTELLER
|
||||
SSgamemode.admin_panel(usr) // BUBBER EDIT - STORYTELLER
|
||||
SSgamemode.ui_interact(usr) // BUBBER EDIT - STORYTELLER
|
||||
else if(href_list["call_shuttle"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/datum/round_event_control/proc/generate_ui_data()
|
||||
return list(
|
||||
"name" = name,
|
||||
"desc" = description,
|
||||
"tags" = tags,
|
||||
"occurences" = get_occurences(),
|
||||
"occurences_shared" = !isnull(shared_occurence_type),
|
||||
"min_pop" = min_players,
|
||||
"start" = (earliest_start / 600),
|
||||
"can_run" = can_spawn_event(),
|
||||
"weight" = calculated_weight,
|
||||
"weight_raw" = weight,
|
||||
"track" = track,
|
||||
"roundstart" = roundstart,
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
#define INIT_ORDER_GAMEMODE 70
|
||||
|
||||
SUBSYSTEM_DEF(gamemode)
|
||||
name = "Gamemode"
|
||||
name = "Storyteller"
|
||||
init_order = INIT_ORDER_GAMEMODE
|
||||
runlevels = RUNLEVEL_GAME
|
||||
flags = SS_BACKGROUND | SS_KEEP_TIMING
|
||||
@@ -119,7 +119,7 @@ SUBSYSTEM_DEF(gamemode)
|
||||
var/roundstart_event_view = TRUE
|
||||
|
||||
/// Whether the storyteller has been halted
|
||||
var/halted_storyteller = FALSE
|
||||
var/storyteller_halted = FALSE
|
||||
|
||||
/// Ready players for roundstart events.
|
||||
var/ready_players = 0
|
||||
@@ -129,6 +129,9 @@ SUBSYSTEM_DEF(gamemode)
|
||||
var/sec_crew = 0
|
||||
var/med_crew = 0
|
||||
|
||||
/// Whether we looked up pop info in this process tick
|
||||
var/pop_data_cached = FALSE
|
||||
|
||||
var/wizardmode = FALSE
|
||||
|
||||
var/storyteller_voted = FALSE
|
||||
@@ -175,11 +178,13 @@ SUBSYSTEM_DEF(gamemode)
|
||||
sch_event.alerted_admins = TRUE
|
||||
message_admins("Scheduled Event: [sch_event.event] will run in [(sch_event.start_time - world.time) / 10] seconds. (<a href='?src=[REF(sch_event)];action=cancel'>CANCEL</a>) (<a href='?src=[REF(sch_event)];action=refund'>REFUND</a>)")
|
||||
|
||||
if(!halted_storyteller && next_storyteller_process <= world.time && storyteller)
|
||||
// We update crew information here to adjust population scalling and event thresholds for the storyteller.
|
||||
if(!storyteller_halted && next_storyteller_process <= world.time && storyteller)
|
||||
// We update crew information here to adjust population scalling and event thresholds for the storyteller.
|
||||
update_crew_infos()
|
||||
next_storyteller_process = world.time + STORYTELLER_WAIT_TIME
|
||||
storyteller.process(STORYTELLER_WAIT_TIME * 0.1)
|
||||
// Reset the cache value to false
|
||||
pop_data_cached = FALSE
|
||||
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/currentrun = src.currentrun
|
||||
@@ -277,10 +282,12 @@ SUBSYSTEM_DEF(gamemode)
|
||||
/// Gets the correct popcount, returning READY people if roundstart, and active people if not.
|
||||
/datum/controller/subsystem/gamemode/proc/get_correct_popcount()
|
||||
if(SSticker.HasRoundStarted())
|
||||
update_crew_infos()
|
||||
if(!pop_data_cached)
|
||||
update_crew_infos()
|
||||
return active_players
|
||||
else
|
||||
calculate_ready_players()
|
||||
if(!pop_data_cached)
|
||||
calculate_ready_players()
|
||||
return ready_players
|
||||
|
||||
/// Refunds and removes a scheduled event.
|
||||
@@ -305,10 +312,11 @@ SUBSYSTEM_DEF(gamemode)
|
||||
for(var/mob/dead/new_player/player as anything in GLOB.new_player_list)
|
||||
if(player.ready == PLAYER_READY_TO_PLAY)
|
||||
ready_players++
|
||||
pop_data_cached = TRUE
|
||||
|
||||
/// We roll points to be spent for roundstart events, including antagonists.
|
||||
/datum/controller/subsystem/gamemode/proc/roll_pre_setup_points()
|
||||
if(storyteller.disable_distribution || halted_storyteller)
|
||||
if(storyteller.disable_distribution || storyteller_halted)
|
||||
return
|
||||
/// Distribute points
|
||||
for(var/track in event_track_points)
|
||||
@@ -343,7 +351,7 @@ SUBSYSTEM_DEF(gamemode)
|
||||
/datum/controller/subsystem/gamemode/proc/handle_pre_setup_roundstart_events()
|
||||
if(storyteller.disable_distribution)
|
||||
return
|
||||
if(halted_storyteller)
|
||||
if(storyteller_halted)
|
||||
message_admins("WARNING: Didn't roll roundstart events (including antagonists) due to the storyteller being halted.")
|
||||
return
|
||||
while(TRUE)
|
||||
@@ -397,6 +405,7 @@ SUBSYSTEM_DEF(gamemode)
|
||||
med_crew++
|
||||
if(player_role.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY)
|
||||
sec_crew++
|
||||
pop_data_cached = TRUE
|
||||
|
||||
/datum/controller/subsystem/gamemode/proc/TriggerEvent(datum/round_event_control/event)
|
||||
. = event.preRunEvent()
|
||||
@@ -511,6 +520,7 @@ SUBSYSTEM_DEF(gamemode)
|
||||
addtimer(CALLBACK(src, PROC_REF(send_trait_report)), rand(1 MINUTES, 5 MINUTES))
|
||||
handle_post_setup_roundstart_events()
|
||||
roundstart_event_view = FALSE
|
||||
pop_data_cached = FALSE // Uncache it because we'd still wrongly consider it cached from lobby pops
|
||||
return TRUE
|
||||
|
||||
|
||||
@@ -680,16 +690,24 @@ SUBSYSTEM_DEF(gamemode)
|
||||
break
|
||||
|
||||
/datum/controller/subsystem/gamemode/proc/init_storyteller()
|
||||
if(storyteller) // If this is true, then an admin bussed one, don't overwrite it
|
||||
log_dynamic("Roundstart storyteller has been set by admins to [storyteller.name], the vote was not considered.")
|
||||
return
|
||||
var/datum/storyteller/storyteller_pick
|
||||
if(!voted_storyteller)
|
||||
storyteller_pick = pick(storytellers)
|
||||
message_admins("We picked [storyteller_pick] for this rounds storyteller, randomly.")
|
||||
log_dynamic("Roundstart picked storyteller [storyteller.name] randomly due to no vote result.")
|
||||
voted_storyteller = storyteller_pick
|
||||
if(storyteller) // If this is true, then an admin bussed one, don't overwrite it
|
||||
return
|
||||
set_storyteller(voted_storyteller)
|
||||
|
||||
/datum/controller/subsystem/gamemode/proc/set_storyteller(passed_type)
|
||||
/**
|
||||
* set_storyteller
|
||||
*
|
||||
* Always call this to set the storyteller
|
||||
* Called by the storyteller system on roundstart and after a vote finishes.
|
||||
* When forced via game panel, forced = TRUE, and force_ckey contains the ckey of the admin who forced it
|
||||
*/
|
||||
/datum/controller/subsystem/gamemode/proc/set_storyteller(passed_type, forced, force_ckey)
|
||||
if(!storytellers[passed_type])
|
||||
message_admins("Attempted to set an invalid storyteller type: [passed_type].")
|
||||
CRASH("Attempted to set an invalid storyteller type: [passed_type].")
|
||||
@@ -704,300 +722,56 @@ SUBSYSTEM_DEF(gamemode)
|
||||
|
||||
to_chat(world, span_notice("<b>Storyteller is [storyteller.name]!</b>"))
|
||||
to_chat(world, span_notice("[storyteller.welcome_text]"))
|
||||
log_admin_private("Storyteller switched to [storyteller.name]. [forced ? "Forced by admin ckey [force_ckey]" : ""]")
|
||||
|
||||
/// Panel containing information, variables and controls about the gamemode and scheduled event
|
||||
/datum/controller/subsystem/gamemode/proc/admin_panel(mob/user)
|
||||
update_crew_infos()
|
||||
var/round_started = SSticker.HasRoundStarted()
|
||||
var/list/dat = list()
|
||||
var/active_pop = get_correct_popcount()
|
||||
dat += "Storyteller: [storyteller ? "[storyteller.name]" : "None"] "
|
||||
dat += " <a href='?src=[REF(src)];panel=main;action=halt_storyteller' [halted_storyteller ? "class='linkOn'" : ""]>HALT Storyteller</a> <a href='?src=[REF(src)];panel=main;action=open_stats'>Event Panel</a> <a href='?src=[REF(src)];panel=main;action=set_storyteller'>Set Storyteller</a> <a href='?src=[REF(src)];panel=main'>Refresh</a>"
|
||||
dat += "<BR><font color='#888888'><i>Storyteller determines points gained, event chances, and is the entity responsible for rolling events.</i></font>"
|
||||
dat += "<BR>Active Players: [active_pop] (Head: [head_crew], Sec: [sec_crew], Eng: [eng_crew], Med: [med_crew]) - Antag Cap: [get_antag_cap()]"
|
||||
dat += "<HR>"
|
||||
dat += "<a href='?src=[REF(src)];panel=main;action=tab;tab=[GAMEMODE_PANEL_MAIN]' [panel_page == GAMEMODE_PANEL_MAIN ? "class='linkOn'" : ""]>Main</a>"
|
||||
dat += " <a href='?src=[REF(src)];panel=main;action=tab;tab=[GAMEMODE_PANEL_VARIABLES]' [panel_page == GAMEMODE_PANEL_VARIABLES ? "class='linkOn'" : ""]>Variables</a>"
|
||||
dat += "<HR>"
|
||||
switch(panel_page)
|
||||
if(GAMEMODE_PANEL_VARIABLES)
|
||||
dat += "<a href='?src=[REF(src)];panel=main;action=reload_config_vars'>Reload Config Vars</a> <font color='#888888'><i>Configs located in game_options.txt.</i></font>"
|
||||
dat += "<BR><b>Point Gains Multipliers (only over time):</b>"
|
||||
dat += "<BR><font color='#888888'><i>This affects points gained over time towards scheduling new events of the tracks.</i></font>"
|
||||
for(var/track in event_tracks)
|
||||
dat += "<BR>[track]: <a href='?src=[REF(src)];panel=main;action=vars;var=pts_multiplier;track=[track]'>[point_gain_multipliers[track]]</a>"
|
||||
dat += "<HR>"
|
||||
/**
|
||||
* halt_storyteller
|
||||
*
|
||||
* Used to halt/unhalt and properly log storyteller
|
||||
*/
|
||||
|
||||
dat += "<b>Roundstart Points Multipliers:</b>"
|
||||
dat += "<BR><font color='#888888'><i>This affects points generated for roundstart events and antagonists.</i></font>"
|
||||
for(var/track in event_tracks)
|
||||
dat += "<BR>[track]: <a href='?src=[REF(src)];panel=main;action=vars;var=roundstart_pts;track=[track]'>[roundstart_point_multipliers[track]]</a>"
|
||||
dat += "<HR>"
|
||||
|
||||
dat += "<b>Minimum Population for Tracks:</b>"
|
||||
dat += "<BR><font color='#888888'><i>This are the minimum population caps for events to be able to run.</i></font>"
|
||||
for(var/track in event_tracks)
|
||||
dat += "<BR>[track]: <a href='?src=[REF(src)];panel=main;action=vars;var=min_pop;track=[track]'>[min_pop_thresholds[track]]</a>"
|
||||
dat += "<HR>"
|
||||
|
||||
dat += "<b>Point Thresholds:</b>"
|
||||
dat += "<BR><font color='#888888'><i>Those are thresholds the tracks require to reach with points to make an event.</i></font>"
|
||||
for(var/track in event_tracks)
|
||||
dat += "<BR>[track]: <a href='?src=[REF(src)];panel=main;action=vars;var=pts_threshold;track=[track]'>[point_thresholds[track]]</a>"
|
||||
|
||||
if(GAMEMODE_PANEL_MAIN)
|
||||
var/even = TRUE
|
||||
dat += "<h2>Event Tracks:</h2>"
|
||||
dat += "<font color='#888888'><i>Every track represents progression towards scheduling an event of it's severity</i></font>"
|
||||
dat += "<table align='center'; width='100%'; height='100%'; style='background-color:#13171C'>"
|
||||
dat += "<tr style='vertical-align:top'>"
|
||||
dat += "<td width=25%><b>Track</b></td>"
|
||||
dat += "<td width=20%><b>Progress</b></td>"
|
||||
dat += "<td width=10%><b>Next</b></td>"
|
||||
dat += "<td width=10%><b>Forced</b></td>"
|
||||
dat += "<td width=35%><b>Actions</b></td>"
|
||||
dat += "</tr>"
|
||||
for(var/track in event_tracks)
|
||||
even = !even
|
||||
var/background_cl = even ? "#17191C" : "#23273C"
|
||||
var/lower = event_track_points[track]
|
||||
var/upper = point_thresholds[track]
|
||||
var/percent = round((lower/upper)*100)
|
||||
var/next = 0
|
||||
var/last_points = last_point_gains[track]
|
||||
if(last_points)
|
||||
next = round((upper - lower) / last_points / STORYTELLER_WAIT_TIME * 40 / 6) / 10
|
||||
dat += "<tr style='vertical-align:top; background-color: [background_cl];'>"
|
||||
dat += "<td>[track]</td>" //Track
|
||||
dat += "<td>[percent]% ([lower]/[upper])</td>" //Progress
|
||||
dat += "<td>~[next] m.</td>" //Next
|
||||
var/datum/round_event_control/forced_event = forced_next_events[track]
|
||||
var/forced = forced_event ? "[forced_event.name] <a href='?src=[REF(src)];panel=main;action=track_action;track_action=remove_forced;track=[track]'>X</a>" : ""
|
||||
dat += "<td>[forced]</td>" //Forced
|
||||
dat += "<td><a href='?src=[REF(src)];panel=main;action=track_action;track_action=set_pts;track=[track]'>Set Pts.</a> <a href='?src=[REF(src)];panel=main;action=track_action;track_action=next_event;track=[track]'>Next Event</a></td>" //Actions
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
|
||||
dat += "<h2>Scheduled Events:</h2>"
|
||||
dat += "<table align='center'; width='100%'; height='100%'; style='background-color:#13171C'>"
|
||||
dat += "<tr style='vertical-align:top'>"
|
||||
dat += "<td width=30%><b>Name</b></td>"
|
||||
dat += "<td width=17%><b>Severity</b></td>"
|
||||
dat += "<td width=12%><b>Time</b></td>"
|
||||
dat += "<td width=41%><b>Actions</b></td>"
|
||||
dat += "</tr>"
|
||||
var/sorted_scheduled = list()
|
||||
for(var/datum/scheduled_event/scheduled as anything in scheduled_events)
|
||||
sorted_scheduled[scheduled] = scheduled.start_time
|
||||
sortTim(sorted_scheduled, cmp=/proc/cmp_numeric_asc, associative = TRUE)
|
||||
even = TRUE
|
||||
for(var/datum/scheduled_event/scheduled as anything in sorted_scheduled)
|
||||
even = !even
|
||||
var/background_cl = even ? "#17191C" : "#23273C"
|
||||
dat += "<tr style='vertical-align:top; background-color: [background_cl];'>"
|
||||
dat += "<td>[scheduled.event.name]</td>" //Name
|
||||
dat += "<td>[scheduled.event.track]</td>" //Severity
|
||||
var/time = (scheduled.event.roundstart && !round_started) ? "ROUNDSTART" : "[(scheduled.start_time - world.time) / (1 SECONDS)] s."
|
||||
dat += "<td>[time]</td>" //Time
|
||||
dat += "<td>[scheduled.get_href_actions()]</td>" //Actions
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
|
||||
dat += "<h2>Running Events:</h2>"
|
||||
dat += "<table align='center'; width='100%'; height='100%'; style='background-color:#13171C'>"
|
||||
dat += "<tr style='vertical-align:top'>"
|
||||
dat += "<td width=30%><b>Name</b></td>"
|
||||
dat += "<td width=70%><b>Actions</b></td>"
|
||||
dat += "</tr>"
|
||||
even = TRUE
|
||||
for(var/datum/round_event/event as anything in running)
|
||||
even = !even
|
||||
var/background_cl = even ? "#17191C" : "#23273C"
|
||||
dat += "<tr style='vertical-align:top; background-color: [background_cl];'>"
|
||||
dat += "<td>[event.control.name]</td>" //Name
|
||||
dat += "<td>-TBA-</td>" //Actions
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
|
||||
var/datum/browser/popup = new(user, "gamemode_admin_panel", "Gamemode Panel", 670, 650)
|
||||
popup.set_content(dat.Join())
|
||||
popup.open()
|
||||
|
||||
/// Panel containing information and actions regarding events
|
||||
/datum/controller/subsystem/gamemode/proc/event_panel(mob/user)
|
||||
var/list/dat = list()
|
||||
if(storyteller)
|
||||
dat += "Storyteller: [storyteller.name]"
|
||||
dat += "<BR>Repetition penalty multiplier: [storyteller.event_repetition_multiplier]"
|
||||
dat += "<BR>Cost variance: [storyteller.cost_variance]"
|
||||
if(storyteller.tag_multipliers)
|
||||
dat += "<BR>Tag multipliers:"
|
||||
for(var/tag in storyteller.tag_multipliers)
|
||||
dat += "[tag]:[storyteller.tag_multipliers[tag]] | "
|
||||
storyteller.calculate_weights(statistics_track_page)
|
||||
else
|
||||
dat += "Storyteller: None<BR>Weight and chance statistics will be inaccurate due to the present lack of a storyteller."
|
||||
dat += "<BR><a href='?src=[REF(src)];panel=stats;action=set_roundstart'[roundstart_event_view ? "class='linkOn'" : ""]>Roundstart Events</a> Forced Roundstart events will use rolled points, and are guaranteed to trigger (even if the used points are not enough)"
|
||||
dat += "<BR>Avg. event intervals: "
|
||||
for(var/track in event_tracks)
|
||||
if(last_point_gains[track])
|
||||
var/est_time = round(point_thresholds[track] / last_point_gains[track] / STORYTELLER_WAIT_TIME * 40 / 6) / 10
|
||||
dat += "[track]: ~[est_time] m. | "
|
||||
dat += "<HR>"
|
||||
for(var/track in EVENT_PANEL_TRACKS)
|
||||
dat += "<a href='?src=[REF(src)];panel=stats;action=set_cat;cat=[track]'[(statistics_track_page == track) ? "class='linkOn'" : ""]>[track]</a>"
|
||||
dat += "<HR>"
|
||||
/// Create event info and stats table
|
||||
dat += "<table align='center'; width='100%'; height='100%'; style='background-color:#13171C'>"
|
||||
dat += "<tr style='vertical-align:top'>"
|
||||
dat += "<td width=17%><b>Name</b></td>"
|
||||
dat += "<td width=16%><b>Tags</b></td>"
|
||||
dat += "<td width=8%><b>Occurences</b></td>"
|
||||
dat += "<td width=5%><b>M.Pop</b></td>"
|
||||
dat += "<td width=5%><b>M.Time</b></td>"
|
||||
dat += "<td width=7%><b>Can Occur</b></td>"
|
||||
dat += "<td width=16%><b>Weight</b></td>"
|
||||
dat += "<td width=26%><b>Actions</b></td>"
|
||||
dat += "</tr>"
|
||||
var/even = TRUE
|
||||
var/total_weight = 0
|
||||
var/list/event_lookup
|
||||
switch(statistics_track_page)
|
||||
if(ALL_EVENTS)
|
||||
event_lookup = control
|
||||
if(UNCATEGORIZED_EVENTS)
|
||||
event_lookup = uncategorized
|
||||
else
|
||||
event_lookup = event_pools[statistics_track_page]
|
||||
var/list/assoc_spawn_weight = list()
|
||||
var/active_pop = get_correct_popcount()
|
||||
for(var/datum/round_event_control/event as anything in event_lookup)
|
||||
if(event.roundstart != roundstart_event_view)
|
||||
continue
|
||||
if(event.can_spawn_event(active_pop))
|
||||
total_weight += event.calculated_weight
|
||||
assoc_spawn_weight[event] = event.calculated_weight
|
||||
else
|
||||
assoc_spawn_weight[event] = 0
|
||||
sortTim(assoc_spawn_weight, cmp=/proc/cmp_numeric_dsc, associative = TRUE)
|
||||
for(var/datum/round_event_control/event as anything in assoc_spawn_weight)
|
||||
even = !even
|
||||
var/background_cl = even ? "#17191C" : "#23273C"
|
||||
dat += "<tr style='vertical-align:top; background-color: [background_cl];'>"
|
||||
dat += "<td>[event.name]</td>" //Name
|
||||
dat += "<td>" //Tags
|
||||
for(var/tag in event.tags)
|
||||
dat += "[tag] "
|
||||
dat += "</td>"
|
||||
var/occurence_string = "[event.occurrences]"
|
||||
if(event.shared_occurence_type)
|
||||
occurence_string += " (shared: [event.get_occurences()])"
|
||||
dat += "<td>[occurence_string]</td>" //Occurences
|
||||
dat += "<td>[event.min_players]</td>" //Minimum pop
|
||||
dat += "<td>[event.earliest_start / (1 MINUTES)] m.</td>" //Minimum time
|
||||
dat += "<td>[assoc_spawn_weight[event] ? "Yes" : "No"]</td>" //Can happen?
|
||||
var/weight_string = "([event.calculated_weight] /raw.[event.weight])"
|
||||
if(assoc_spawn_weight[event])
|
||||
var/percent = round((event.calculated_weight / total_weight) * 100)
|
||||
weight_string = "[percent]% - [weight_string]"
|
||||
dat += "<td>[weight_string]</td>" //Weight
|
||||
dat += "<td>[event.get_href_actions()]</td>" //Actions
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
var/datum/browser/popup = new(user, "gamemode_event_panel", "Event Panel", 1000, 600)
|
||||
popup.set_content(dat.Join())
|
||||
popup.open()
|
||||
|
||||
/datum/controller/subsystem/gamemode/Topic(href, href_list)
|
||||
. = ..()
|
||||
var/mob/user = usr
|
||||
if(!check_rights(R_ADMIN))
|
||||
/datum/controller/subsystem/gamemode/proc/halt_storyteller(mob/user)
|
||||
storyteller_halted = !storyteller_halted
|
||||
if(isnull(user))
|
||||
return
|
||||
switch(href_list["panel"])
|
||||
if("main")
|
||||
switch(href_list["action"])
|
||||
if("set_storyteller")
|
||||
message_admins("[key_name_admin(usr)] is picking a new Storyteller.")
|
||||
var/list/name_list = list()
|
||||
for(var/storyteller_type in storytellers)
|
||||
var/datum/storyteller/storyboy = storytellers[storyteller_type]
|
||||
name_list[storyboy.name] = storyboy.type
|
||||
var/new_storyteller_name = input(usr, "Choose new storyteller (circumvents voted one):", "Storyteller") as null|anything in name_list
|
||||
if(!new_storyteller_name)
|
||||
message_admins("[key_name_admin(usr)] has cancelled picking a Storyteller.")
|
||||
return
|
||||
message_admins("[key_name_admin(usr)] has chosen [new_storyteller_name] as the new Storyteller.")
|
||||
var/new_storyteller_type = name_list[new_storyteller_name]
|
||||
set_storyteller(new_storyteller_type)
|
||||
if("halt_storyteller")
|
||||
halted_storyteller = !halted_storyteller
|
||||
message_admins("[key_name_admin(usr)] has [halted_storyteller ? "HALTED" : "un-halted"] the Storyteller.")
|
||||
if("vars")
|
||||
var/track = href_list["track"]
|
||||
switch(href_list["var"])
|
||||
if("pts_multiplier")
|
||||
var/new_value = input(usr, "New value:", "Set new value") as num|null
|
||||
if(isnull(new_value) || new_value < 0)
|
||||
return
|
||||
message_admins("[key_name_admin(usr)] set point gain multiplier for [track] track to [new_value].")
|
||||
point_gain_multipliers[track] = new_value
|
||||
if("roundstart_pts")
|
||||
var/new_value = input(usr, "New value:", "Set new value") as num|null
|
||||
if(isnull(new_value) || new_value < 0)
|
||||
return
|
||||
message_admins("[key_name_admin(usr)] set roundstart pts multiplier for [track] track to [new_value].")
|
||||
roundstart_point_multipliers[track] = new_value
|
||||
if("min_pop")
|
||||
var/new_value = input(usr, "New value:", "Set new value") as num|null
|
||||
if(isnull(new_value) || new_value < 0)
|
||||
return
|
||||
message_admins("[key_name_admin(usr)] set minimum population for [track] track to [new_value].")
|
||||
min_pop_thresholds[track] = new_value
|
||||
if("pts_threshold")
|
||||
var/new_value = input(usr, "New value:", "Set new value") as num|null
|
||||
if(isnull(new_value) || new_value < 0)
|
||||
return
|
||||
message_admins("[key_name_admin(usr)] set point threshold of [track] track to [new_value].")
|
||||
point_thresholds[track] = new_value
|
||||
if("reload_config_vars")
|
||||
message_admins("[key_name_admin(usr)] reloaded gamemode config vars.")
|
||||
load_config_vars()
|
||||
if("tab")
|
||||
var/tab = href_list["tab"]
|
||||
panel_page = tab
|
||||
if("open_stats")
|
||||
event_panel(user)
|
||||
return
|
||||
if("track_action")
|
||||
var/track = href_list["track"]
|
||||
if(!(track in event_tracks))
|
||||
return
|
||||
switch(href_list["track_action"])
|
||||
if("remove_forced")
|
||||
if(forced_next_events[track])
|
||||
var/datum/round_event_control/event = forced_next_events[track]
|
||||
message_admins("[key_name_admin(usr)] removed forced event [event.name] from track [track].")
|
||||
forced_next_events -= track
|
||||
if("set_pts")
|
||||
var/set_pts = input(usr, "New point amount ([point_thresholds[track]]+ invokes event):", "Set points for [track]") as num|null
|
||||
if(isnull(set_pts))
|
||||
return
|
||||
event_track_points[track] = set_pts
|
||||
message_admins("[key_name_admin(usr)] set points of [track] track to [set_pts].")
|
||||
log_admin_private("[key_name(usr)] set points of [track] track to [set_pts].")
|
||||
if("next_event")
|
||||
message_admins("[key_name_admin(usr)] invoked next event for [track] track.")
|
||||
log_admin_private("[key_name(usr)] invoked next event for [track] track.")
|
||||
event_track_points[track] = point_thresholds[track]
|
||||
if(storyteller)
|
||||
storyteller.handle_tracks()
|
||||
admin_panel(user)
|
||||
if("stats")
|
||||
switch(href_list["action"])
|
||||
if("set_roundstart")
|
||||
roundstart_event_view = !roundstart_event_view
|
||||
if("set_cat")
|
||||
var/new_category = href_list["cat"]
|
||||
if(new_category in EVENT_PANEL_TRACKS)
|
||||
statistics_track_page = new_category
|
||||
event_panel(user)
|
||||
message_admins("[key_name_admin(user)] has [storyteller_halted ? "HALTED" : "un-halted"] the Storyteller.")
|
||||
log_dynamic("Storyteller [storyteller_halted ? "halted" : "un-halted"] by admin [user.ckey].")
|
||||
|
||||
/**
|
||||
* force_next_event
|
||||
*
|
||||
* Forces next event scheduling/firing for `track`
|
||||
*
|
||||
*/
|
||||
/datum/controller/subsystem/gamemode/proc/force_next_event(track, mob/user)
|
||||
if(isnull(user))
|
||||
return
|
||||
if(isnull(storyteller))
|
||||
return
|
||||
event_track_points[track] = point_thresholds[track]
|
||||
storyteller.handle_tracks()
|
||||
message_admins("[key_name_admin(user)] has forced an event for the [track] track.")
|
||||
log_admin_private("Storyteller track [track] forced to run an event by [user.ckey]")
|
||||
|
||||
/**
|
||||
* get_scheduled_by_event_type
|
||||
*
|
||||
* Returns the scheduled event, if any, by the event's type.
|
||||
* Returns null if no such event exists.
|
||||
*/
|
||||
/datum/controller/subsystem/gamemode/proc/get_scheduled_by_event_type(type)
|
||||
for(var/datum/scheduled_event/scheduled_event as anything in scheduled_events)
|
||||
if(scheduled_event.event.type == text2path(type))
|
||||
return scheduled_event
|
||||
return null
|
||||
|
||||
/datum/controller/subsystem/gamemode/proc/get_event_by_track_and_type(track, type)
|
||||
if(isnull(track) || isnull(type))
|
||||
return
|
||||
var/list/track_events = event_pools[track]
|
||||
if(isnull(track_events))
|
||||
return
|
||||
|
||||
for(var/datum/round_event_control/event as anything in track_events)
|
||||
if(event.type == text2path(type))
|
||||
return event
|
||||
|
||||
157
modular_zubbers/code/modules/storyteller/gamemode_ui.dm
Normal file
157
modular_zubbers/code/modules/storyteller/gamemode_ui.dm
Normal file
@@ -0,0 +1,157 @@
|
||||
// Open the ui, nothing special here
|
||||
// Maybe make it update static data?
|
||||
/datum/controller/subsystem/gamemode/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ZubbersStoryteller", "Storyteller control panel")
|
||||
ui.open()
|
||||
|
||||
/datum/controller/subsystem/gamemode/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["storyteller_name"] = storyteller ? storyteller.name : "None"
|
||||
data["storyteller_halt"] = storyteller_halted
|
||||
data["antag_count"] = GLOB.current_living_antags.len // Switch this up if the calculation for the cap changes (It probably will)
|
||||
data["antag_cap"] = get_antag_cap()
|
||||
|
||||
data["pop_data"] = get_ui_pop_data()
|
||||
data["tracks_data"] = get_ui_track_data()
|
||||
data["scheduled_data"] = get_ui_scheduled_data()
|
||||
return data
|
||||
|
||||
/datum/controller/subsystem/gamemode/proc/get_ui_pop_data()
|
||||
var/list/pop_data = list(
|
||||
"active" = get_correct_popcount(),
|
||||
"head" = head_crew,
|
||||
"sec" = sec_crew,
|
||||
"eng" = eng_crew,
|
||||
"med" = med_crew,
|
||||
)
|
||||
return pop_data
|
||||
|
||||
/datum/controller/subsystem/gamemode/proc/get_ui_track_data()
|
||||
var/list/track_data = list()
|
||||
for(var/track in event_tracks)
|
||||
var/last_points = last_point_gains[track]
|
||||
var/lower = event_track_points[track]
|
||||
var/upper = point_thresholds[track]
|
||||
var/next = last_points ? round((upper - lower) / last_points / STORYTELLER_WAIT_TIME * 40 / 6) / 10 : 0
|
||||
var/datum/round_event_control/forced = forced_next_events[track]
|
||||
track_data[track] = list(
|
||||
"name" = "[track]",
|
||||
"current" = lower,
|
||||
"max" = upper,
|
||||
"next" = next,
|
||||
"forced" = forced ? forced.generate_ui_data() : null
|
||||
)
|
||||
return track_data
|
||||
|
||||
/datum/controller/subsystem/gamemode/proc/get_ui_scheduled_data()
|
||||
var/list/scheduled_data = list()
|
||||
var/list/sorted_scheduled = list()
|
||||
for(var/datum/scheduled_event/scheduled as anything in scheduled_events)
|
||||
sorted_scheduled[scheduled] = scheduled.start_time
|
||||
sortTim(sorted_scheduled, cmp=/proc/cmp_numeric_asc, associative = TRUE)
|
||||
for(var/datum/scheduled_event/scheduled as anything in sorted_scheduled)
|
||||
var/time = (scheduled.event.roundstart) ? null : ((scheduled.start_time - world.time) / (1 SECONDS))
|
||||
scheduled_data[scheduled.event.name] = list(
|
||||
"track" = scheduled.event.track,
|
||||
"time" = time,
|
||||
"event_type" = scheduled.event.type,
|
||||
)
|
||||
return scheduled_data
|
||||
|
||||
// God has abandoned us
|
||||
/datum/controller/subsystem/gamemode/ui_static_data(mob/user)
|
||||
var/list/static_data = list()
|
||||
// Events are static because we don't need to update them as often, only on storyteller ticks
|
||||
static_data["events"] = list()
|
||||
for(var/event_category as anything in event_pools)
|
||||
var/list/event_list = event_pools[event_category]
|
||||
static_data["events"][event_category] = list("name" = event_category, "events" = list())
|
||||
for(var/datum/round_event_control/event as anything in event_list)
|
||||
static_data["events"][event_category]["events"][event.type] = event.generate_ui_data()
|
||||
// Uncategorized shit
|
||||
static_data["events"]["Uncategorized"] = list("name" = "Uncategorized", "events" = list())
|
||||
for(var/datum/round_event_control/event as anything in uncategorized)
|
||||
static_data["events"]["Uncategorized"]["events"][event.type] = event.generate_ui_data()
|
||||
return static_data
|
||||
|
||||
/datum/controller/subsystem/gamemode/ui_state(mob/user)
|
||||
return GLOB.admin_state
|
||||
|
||||
/datum/controller/subsystem/gamemode/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
switch(action)
|
||||
if("set_storyteller")
|
||||
// Todo: Replace with tgui_input
|
||||
var/list/name_list = list()
|
||||
for(var/storyteller_type in storytellers)
|
||||
var/datum/storyteller/storyboy = storytellers[storyteller_type]
|
||||
name_list[storyboy.name] = storyboy.type
|
||||
var/new_storyteller_name = input(usr, "Choose new storyteller (circumvents voted one):", "Storyteller") as null|anything in name_list
|
||||
if(!new_storyteller_name)
|
||||
return
|
||||
message_admins("[key_name_admin(usr)] has changed the Storyteller to [new_storyteller_name].")
|
||||
var/new_storyteller_type = name_list[new_storyteller_name]
|
||||
set_storyteller(new_storyteller_type, TRUE, usr.ckey)
|
||||
if("halt_storyteller")
|
||||
halt_storyteller(usr)
|
||||
if("track_action")
|
||||
switch(params["action"])
|
||||
if("set_pnts")
|
||||
var/track_to_adjust = params["track"]
|
||||
var/num = tgui_input_number(\
|
||||
usr, \
|
||||
"Set [track_to_adjust] track points",
|
||||
title = "Track points", \
|
||||
default = event_track_points[track_to_adjust], \
|
||||
max_value = point_thresholds[track_to_adjust]*5, \
|
||||
)
|
||||
if(isnull(num))
|
||||
return
|
||||
event_track_points[track_to_adjust] = num
|
||||
if("force_next")
|
||||
var/forced_track = params["track"]
|
||||
force_next_event(forced_track, usr)
|
||||
if("event_action")
|
||||
var/datum/scheduled_event/sch_event = get_scheduled_by_event_type(params["type"])
|
||||
if(isnull(sch_event))
|
||||
return
|
||||
switch(params["action"])
|
||||
if("cancel")
|
||||
message_admins("[key_name_admin(usr)] cancelled scheduled event [sch_event.event.name].")
|
||||
log_admin_private("[key_name(usr)] cancelled scheduled event [sch_event.event.name].")
|
||||
SSgamemode.remove_scheduled_event(sch_event)
|
||||
if("refund")
|
||||
message_admins("[key_name_admin(usr)] refunded scheduled event [sch_event.event.name].")
|
||||
log_admin_private("[key_name(usr)] refunded scheduled event [sch_event.event.name].")
|
||||
SSgamemode.refund_scheduled_event(sch_event)
|
||||
if("reschedule")
|
||||
var/new_schedule = tgui_input_number(usr, "Set time in seconds in which to fire event", "Rescheduling event", 0, 3600, 0)
|
||||
if(isnull(new_schedule) || QDELETED(sch_event))
|
||||
return
|
||||
sch_event.start_time = world.time + (new_schedule SECONDS)
|
||||
message_admins("[key_name_admin(usr)] rescheduled event [sch_event.event.name] to [new_schedule] seconds.")
|
||||
log_admin_private("[key_name(usr)] rescheduled event [sch_event.event.name] to [new_schedule] seconds.")
|
||||
if("fire")
|
||||
if(!SSticker.HasRoundStarted())
|
||||
return
|
||||
message_admins("[key_name_admin(usr)] has fired scheduled event [sch_event.event.name].")
|
||||
log_admin_private("[key_name(usr)] has fired scheduled event [sch_event.event.name].")
|
||||
sch_event.try_fire()
|
||||
if("panel_action")
|
||||
var/datum/round_event_control/event = get_event_by_track_and_type(params["track"], params["type"])
|
||||
if(isnull(event))
|
||||
return
|
||||
switch(params["action"])
|
||||
if("fire")
|
||||
message_admins("[key_name_admin(usr)] has fired event [src.name].")
|
||||
log_admin_private("[key_name(usr)] has fired event [src.name].")
|
||||
SSgamemode.TriggerEvent(event)
|
||||
if("force_next")
|
||||
message_admins("[key_name_admin(usr)] has forced scheduled event [src.name].")
|
||||
log_admin_private("[key_name(usr)] has forced scheduled event [src.name].")
|
||||
SSgamemode.force_event(event)
|
||||
if("panel_update")
|
||||
if(storyteller)
|
||||
storyteller.calculate_weights_all()
|
||||
@@ -66,31 +66,3 @@
|
||||
remove_occurence()
|
||||
event = null
|
||||
return ..()
|
||||
|
||||
/datum/scheduled_event/Topic(href, href_list)
|
||||
. = ..()
|
||||
if(QDELETED(src))
|
||||
return
|
||||
var/round_started = SSticker.HasRoundStarted()
|
||||
switch(href_list["action"])
|
||||
if("cancel")
|
||||
message_admins("[key_name_admin(usr)] cancelled scheduled event [event.name].")
|
||||
log_admin_private("[key_name(usr)] cancelled scheduled event [event.name].")
|
||||
SSgamemode.remove_scheduled_event(src)
|
||||
if("refund")
|
||||
message_admins("[key_name_admin(usr)] refunded scheduled event [event.name].")
|
||||
log_admin_private("[key_name(usr)] refunded scheduled event [event.name].")
|
||||
SSgamemode.refund_scheduled_event(src)
|
||||
if("reschedule")
|
||||
var/new_schedule = input(usr, "New schedule time (in seconds):", "Reschedule Event") as num|null
|
||||
if(isnull(new_schedule) || QDELETED(src))
|
||||
return
|
||||
start_time = world.time + new_schedule * 1 SECONDS
|
||||
message_admins("[key_name_admin(usr)] rescheduled event [event.name] to [new_schedule] seconds.")
|
||||
log_admin_private("[key_name(usr)] rescheduled event [event.name] to [new_schedule] seconds.")
|
||||
if("fire")
|
||||
if(!round_started)
|
||||
return
|
||||
message_admins("[key_name_admin(usr)] has fired scheduled event [event.name].")
|
||||
log_admin_private("[key_name(usr)] has fired scheduled event [event.name].")
|
||||
try_fire()
|
||||
|
||||
@@ -65,7 +65,11 @@
|
||||
mode.event_track_points[track] += point_gain
|
||||
mode.last_point_gains[track] = point_gain
|
||||
|
||||
/// Goes through every track of the gamemode and checks if it passes a threshold to buy an event, if does, buys one.
|
||||
/**
|
||||
* Goes through every track of the gamemode and checks if it passes a threshold to buy an event, if does, buys one.
|
||||
*
|
||||
* Additionally updates static ui data once it's done incase event track data has changed
|
||||
*/
|
||||
/datum/storyteller/proc/handle_tracks()
|
||||
. = FALSE //Has return value for the roundstart loop
|
||||
var/datum/controller/subsystem/gamemode/mode = SSgamemode
|
||||
@@ -73,6 +77,7 @@
|
||||
var/points = mode.event_track_points[track]
|
||||
if(points >= mode.point_thresholds[track] && find_and_buy_event_from_track(track))
|
||||
. = TRUE
|
||||
mode.update_static_data_for_all_viewers()
|
||||
|
||||
/// Find and buy a valid event from a track.
|
||||
/datum/storyteller/proc/find_and_buy_event_from_track(track)
|
||||
@@ -128,7 +133,9 @@
|
||||
else
|
||||
mode.schedule_event(bought_event, (rand(3, 4) MINUTES), total_cost)
|
||||
|
||||
/// Calculates the weights of the events from a passed track.
|
||||
/**
|
||||
* Calculates the weights of the events from a passed track.
|
||||
*/
|
||||
/datum/storyteller/proc/calculate_weights(track)
|
||||
for(var/datum/round_event_control/event as anything in SSgamemode.event_pools[track])
|
||||
var/weight_total = event.weight
|
||||
@@ -144,3 +151,9 @@
|
||||
weight_total -= event.reoccurence_penalty_multiplier * weight_total * (1 - (event_repetition_multiplier ** occurences))
|
||||
/// Write it
|
||||
event.calculated_weight = round(weight_total, 1)
|
||||
|
||||
/datum/storyteller/proc/calculate_weights_all()
|
||||
var/datum/controller/subsystem/gamemode/mode = SSgamemode
|
||||
for(var/track in mode.event_tracks)
|
||||
calculate_weights(track)
|
||||
mode.update_static_data_for_all_viewers()
|
||||
|
||||
2
modular_zubbers/code/modules/storyteller/verbs.dm
Normal file
2
modular_zubbers/code/modules/storyteller/verbs.dm
Normal file
@@ -0,0 +1,2 @@
|
||||
ADMIN_VERB(storyteller_panel, R_ADMIN, "Storyteller Panel", "Control panel for the Storyteller.", ADMIN_CATEGORY_GAME)
|
||||
SSgamemode.ui_interact(usr)
|
||||
@@ -9275,9 +9275,12 @@
|
||||
#include "modular_zubbers\code\modules\storyteller\config.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\divergency_report.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\gamemode.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\gamemode_ui.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\scheduled_event.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\storyteller_vote.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\verbs.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\_events\_event.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\_events\_event_ui.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\_events\scrubber_overflow.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\event_defines\disabled_event_overrides.dm"
|
||||
#include "modular_zubbers\code\modules\storyteller\event_defines\crewset\_antagonist_event.dm"
|
||||
|
||||
470
tgui/packages/tgui/interfaces/ZubbersStoryteller.tsx
Normal file
470
tgui/packages/tgui/interfaces/ZubbersStoryteller.tsx
Normal file
@@ -0,0 +1,470 @@
|
||||
import { useState } from 'react';
|
||||
import { Tooltip } from 'tgui-core/components';
|
||||
|
||||
import { useBackend } from '../backend';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
LabeledList,
|
||||
ProgressBar,
|
||||
Section,
|
||||
Stack,
|
||||
Table,
|
||||
} from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export type Storyteller_Data = {
|
||||
storyteller_name: string;
|
||||
storyteller_halt: Boolean;
|
||||
antag_count: number;
|
||||
antag_cap: number;
|
||||
|
||||
pop_data: Record<string, number>;
|
||||
tracks_data: Record<string, Storyteller_Track>;
|
||||
scheduled_data: Record<string, Record<string, string>>;
|
||||
events: Record<string, Storyteller_Event_Category>;
|
||||
};
|
||||
|
||||
export type Storyteller_Track = {
|
||||
name: string;
|
||||
current: number;
|
||||
max: number;
|
||||
next: number;
|
||||
forced: Storyteller_Event;
|
||||
};
|
||||
|
||||
export type Storyteller_Event = {
|
||||
name: string;
|
||||
desc: string;
|
||||
tags: string[];
|
||||
occurences: number;
|
||||
occurences_shared: Boolean;
|
||||
min_pop: number;
|
||||
start: number;
|
||||
can_run: Boolean;
|
||||
weight: number;
|
||||
weight_raw: number;
|
||||
track: string;
|
||||
roundstart: boolean;
|
||||
};
|
||||
|
||||
export type Storyteller_Event_Category = {
|
||||
name: string;
|
||||
events: Record<string, Storyteller_Event>;
|
||||
};
|
||||
|
||||
export const ZubbersStoryteller = (props) => {
|
||||
return (
|
||||
<Window width={1200} height={680}>
|
||||
<Window.Content height="100%">
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow>
|
||||
<ZubbersStorytellerRoundData />
|
||||
<Stack.Divider />
|
||||
<ZubbersStorytellerTrackData />
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item grow>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<ZubbersStorytellerScheduledData />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<ZubbersStorytellerEventPanel />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const ZubbersStorytellerRoundData = (props) => {
|
||||
const { act, data } = useBackend<Storyteller_Data>();
|
||||
const {
|
||||
storyteller_name,
|
||||
storyteller_halt,
|
||||
pop_data,
|
||||
antag_cap,
|
||||
antag_count,
|
||||
} = data;
|
||||
return (
|
||||
<Section
|
||||
title="Storyteller"
|
||||
buttons={
|
||||
<>
|
||||
<Box inline bold mr={1}>
|
||||
{storyteller_name}
|
||||
</Box>
|
||||
<Button onClick={() => act('set_storyteller')}>
|
||||
Set Storyteller
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Storyteller status">
|
||||
<Button
|
||||
color={storyteller_halt ? 'red' : 'green'}
|
||||
tooltip={(storyteller_halt ? 'Unhalt' : 'Halt') + ' storyteller'}
|
||||
onClick={() => act('halt_storyteller')}
|
||||
textAlign="center"
|
||||
>
|
||||
{storyteller_halt ? 'Halted' : 'Running'}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Active Players">
|
||||
{pop_data['active']} {'('}Head: {pop_data['head']}, Sec:{' '}
|
||||
{pop_data['sec']}, Eng: {pop_data['eng']}, Med: {pop_data['med']}
|
||||
{')'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Antag Cap"
|
||||
tooltip="Amount of antags / The antag cap"
|
||||
>
|
||||
<ProgressBar
|
||||
value={antag_count}
|
||||
maxValue={antag_cap}
|
||||
ranges={{
|
||||
good: [-Infinity, antag_cap],
|
||||
bad: [antag_cap, Infinity],
|
||||
}}
|
||||
>
|
||||
{antag_count + ' / ' + antag_cap}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const TRACK_DATA_TRACK_WIDTH = '5%';
|
||||
const TRACK_DATA_POINT_WIDTH = '22%';
|
||||
const TRACK_DATA_NEXT_WIDTH = '5%';
|
||||
const TRACK_DATA_FORCED_WIDTH = '20%';
|
||||
const TRACK_DATA_ACTIONS_WIDTH = '20%';
|
||||
|
||||
export const ZubbersStorytellerTrackData = (props) => {
|
||||
const { act, data } = useBackend<Storyteller_Data>();
|
||||
const { tracks_data, storyteller_halt } = data;
|
||||
return (
|
||||
<Section title="Tracks">
|
||||
<Table>
|
||||
<Table.Row bold>
|
||||
<Table.Cell width={TRACK_DATA_TRACK_WIDTH}>Track</Table.Cell>
|
||||
<Table.Cell width={TRACK_DATA_POINT_WIDTH}>Points</Table.Cell>
|
||||
<Table.Cell width={TRACK_DATA_NEXT_WIDTH} textAlign="center">
|
||||
Next event
|
||||
</Table.Cell>
|
||||
<Table.Cell width={TRACK_DATA_FORCED_WIDTH}>
|
||||
Next forced event
|
||||
</Table.Cell>
|
||||
<Table.Cell width={TRACK_DATA_ACTIONS_WIDTH}>Actions</Table.Cell>
|
||||
</Table.Row>
|
||||
<Stack.Divider />
|
||||
{Object.entries(tracks_data).map(([track, track_data]) => {
|
||||
const max_points = track_data.max;
|
||||
const current_points = track_data.current;
|
||||
const forced = track_data.forced ? track_data.forced : 0;
|
||||
return (
|
||||
<Table.Row key={track}>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
tooltip="Edit points"
|
||||
width="100%"
|
||||
textAlign="center"
|
||||
onClick={() =>
|
||||
act('track_action', { action: 'set_pnts', track: track })
|
||||
}
|
||||
>
|
||||
{track}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<ProgressBar
|
||||
value={current_points}
|
||||
maxValue={max_points}
|
||||
ranges={{
|
||||
good: [-Infinity, max_points],
|
||||
average: [max_points, Infinity],
|
||||
}}
|
||||
>
|
||||
{current_points + ' / ' + max_points}
|
||||
{' (' +
|
||||
Math.floor((current_points * 100) / max_points) +
|
||||
'%) '}
|
||||
</ProgressBar>
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign="center">
|
||||
{storyteller_halt ? 'N/A' : '~' + track_data['next'] + 'min'}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{forced ? forced.name : ''}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
onClick={() =>
|
||||
act('track_action', { action: 'force_next', track: track })
|
||||
}
|
||||
>
|
||||
Next Event
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export const ZubbersStorytellerScheduledData = (props) => {
|
||||
const { act, data } = useBackend<Storyteller_Data>();
|
||||
const { scheduled_data } = data;
|
||||
return (
|
||||
<Section title="Scheduled events">
|
||||
<Table>
|
||||
<Table.Row bold>
|
||||
<Table.Cell>Name</Table.Cell>
|
||||
<Table.Cell>Track</Table.Cell>
|
||||
<Table.Cell>Time</Table.Cell>
|
||||
<Table.Cell>Actions</Table.Cell>
|
||||
</Table.Row>
|
||||
{Object.entries(scheduled_data).map(([event_name, event_data]) => {
|
||||
const timeNum = Number(event_data['time'])?.toFixed(1);
|
||||
return (
|
||||
<Table.Row key={event_name}>
|
||||
<Table.Cell>{event_name}</Table.Cell>
|
||||
<Table.Cell>{event_data['track']}</Table.Cell>
|
||||
<Table.Cell>{timeNum ? timeNum + ' s' : 'Roundstart'}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() =>
|
||||
act('event_action', {
|
||||
action: 'cancel',
|
||||
type: event_data['event_type'],
|
||||
})
|
||||
}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
act('event_action', {
|
||||
action: 'refund',
|
||||
type: event_data['event_type'],
|
||||
})
|
||||
}
|
||||
>
|
||||
Refund
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
act('event_action', {
|
||||
action: 'reschedule',
|
||||
type: event_data['event_type'],
|
||||
})
|
||||
}
|
||||
>
|
||||
Reschedule
|
||||
</Button>
|
||||
<Button
|
||||
color="green"
|
||||
onClick={() =>
|
||||
act('event_action', {
|
||||
action: 'fire',
|
||||
type: event_data['event_type'],
|
||||
})
|
||||
}
|
||||
>
|
||||
Fire
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const EVENT_PANEL_NAME_WIDTH = '20%';
|
||||
const EVENT_PANEL_TAGS_WIDTH = '10%';
|
||||
const EVENT_PANEL_OCCURENCES_WIDTH = '6%';
|
||||
const EVENT_PANEL_POP_WIDTH = '6%';
|
||||
const EVENT_PANEL_MINTIME_WIDTH = '6%';
|
||||
const EVENT_PANEL_CANRUN_WIDTH = '6%';
|
||||
const EVENT_PANEL_WEIGHT_WIDTH = '10%';
|
||||
const EVENT_PANEL_ACTIONS_WIDTH = '20%';
|
||||
|
||||
export const ZubbersStorytellerEventPanel = (props) => {
|
||||
const { act, data } = useBackend<Storyteller_Data>();
|
||||
const { events } = data;
|
||||
|
||||
const eventCategoryTabs = Object.values(events);
|
||||
const [currentEventCategory, setCurrentEventCategory] = useState(
|
||||
eventCategoryTabs[0],
|
||||
);
|
||||
const [showRoundstart, setRounstart] = useState(false);
|
||||
|
||||
const eventButtons = () => {
|
||||
const event_categories = Object.values(events).map((event_category) => {
|
||||
return (
|
||||
<Button
|
||||
key={event_category.name}
|
||||
selected={event_category === currentEventCategory}
|
||||
onClick={() => setCurrentEventCategory(event_category)}
|
||||
>
|
||||
{event_category.name}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Stack>
|
||||
<Button.Checkbox
|
||||
checked={showRoundstart}
|
||||
onClick={() => setRounstart((prevValue) => !prevValue)}
|
||||
>
|
||||
Roundstart
|
||||
</Button.Checkbox>
|
||||
{event_categories}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Event Panel"
|
||||
maxHeight="100%"
|
||||
fill
|
||||
scrollable
|
||||
buttons={eventButtons()}
|
||||
>
|
||||
<Table>
|
||||
<Table.Row bold>
|
||||
<Table.Cell width={EVENT_PANEL_NAME_WIDTH}>Name</Table.Cell>
|
||||
<Table.Cell width={EVENT_PANEL_TAGS_WIDTH}>Tags</Table.Cell>
|
||||
<Table.Cell width={EVENT_PANEL_OCCURENCES_WIDTH} textAlign="center">
|
||||
Occ.
|
||||
</Table.Cell>
|
||||
<Table.Cell width={EVENT_PANEL_POP_WIDTH} textAlign="center">
|
||||
M.Pop
|
||||
</Table.Cell>
|
||||
<Table.Cell width={EVENT_PANEL_MINTIME_WIDTH} textAlign="center">
|
||||
M.Time
|
||||
</Table.Cell>
|
||||
<Table.Cell width={EVENT_PANEL_CANRUN_WIDTH} textAlign="center">
|
||||
Can Run
|
||||
</Table.Cell>
|
||||
<Table.Cell width={EVENT_PANEL_WEIGHT_WIDTH} textAlign="center">
|
||||
Weight (Raw)
|
||||
</Table.Cell>
|
||||
<Table.Cell width={EVENT_PANEL_ACTIONS_WIDTH}>Actions</Table.Cell>
|
||||
</Table.Row>
|
||||
<ZubbersStorytellerEventPanelCategory
|
||||
current={currentEventCategory}
|
||||
roundstart={showRoundstart}
|
||||
/>
|
||||
</Table>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
type EventPanel_Category_Props = {
|
||||
current: Storyteller_Event_Category;
|
||||
roundstart: boolean;
|
||||
};
|
||||
|
||||
export const ZubbersStorytellerEventPanelCategory = (
|
||||
props: EventPanel_Category_Props,
|
||||
) => {
|
||||
const { current, roundstart } = props;
|
||||
return (
|
||||
<>
|
||||
{Object.entries(current.events)
|
||||
.sort((a, b) => b[1].weight - a[1].weight)
|
||||
.map(([event_type, event]) => {
|
||||
if (Boolean(event.roundstart) === roundstart) {
|
||||
return (
|
||||
<ZubbersStorytellerEvent
|
||||
key={event_type}
|
||||
type={event_type}
|
||||
event={event}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type Event_Props = {
|
||||
type: string;
|
||||
event: Storyteller_Event;
|
||||
};
|
||||
|
||||
export const ZubbersStorytellerEvent = (props: Event_Props) => {
|
||||
const { type, event } = props;
|
||||
const { act } = useBackend<Storyteller_Data>();
|
||||
return (
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Tooltip content={event.desc}>{event.name}</Tooltip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{Object.values(event.tags).map((tag) => {
|
||||
return tag + ' ';
|
||||
})}
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign="center">
|
||||
{event.occurences}
|
||||
{event.occurences_shared ? 'S' : ''}
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign="center">{event.min_pop}</Table.Cell>
|
||||
<Table.Cell textAlign="center">
|
||||
{event.start}
|
||||
{'m.'}
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign="center">{event.can_run ? 'Yes' : 'No'}</Table.Cell>
|
||||
<Table.Cell textAlign="center">
|
||||
{event.weight}
|
||||
{' (' + event.weight_raw + ')'}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
onClick={() =>
|
||||
act('panel_action', {
|
||||
action: 'fire',
|
||||
type: type,
|
||||
track: event.track,
|
||||
})
|
||||
}
|
||||
>
|
||||
Fire
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
act('panel_action', {
|
||||
action: 'schedule',
|
||||
type: type,
|
||||
track: event.track,
|
||||
})
|
||||
}
|
||||
>
|
||||
Schedule
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
act('panel_action', {
|
||||
action: 'force_next',
|
||||
type: type,
|
||||
track: event.track,
|
||||
})
|
||||
}
|
||||
>
|
||||
Force Next
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user