diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm
index 8f2c0425671..8ce74747102 100644
--- a/code/__HELPERS/maths.dm
+++ b/code/__HELPERS/maths.dm
@@ -13,6 +13,11 @@ var/list/sqrtTable = list(1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4,
8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10)
*/
+
+// Returns y so that y/x = a/b.
+#define RULE_OF_THREE(a, b, x) ((a*x)/b)
+#define tan(x) (sin(x)/cos(x))
+
/proc/Atan2(x, y)
if (!x && !y)
return 0
@@ -35,6 +40,20 @@ proc/arctan(x)
else
return 0
+// -- Returns a Lorentz-distributed number.
+// -- The probability density function has centre x0 and width s.
+
+/proc/lorentz_distribution(var/x0, var/s)
+ var/x = rand()
+ var/y = s*tan_rad(PI*(x-0.5)) + x0
+ return y
+
+// -- Returns the Lorentz cummulative distribution of the real x.
+
+/proc/lorentz_cummulative_distribution(var/x, var/x0, var/s)
+ var/y = (1/PI)*ToRadians(arctan((x-x0)/s)) + 1/2
+ return y
+
//Moved to macros.dm to reduce pure calling overhead, this was being called shitloads, like, most calls of all procs.
/*
/proc/Clamp(const/val, const/min, const/max)
@@ -188,9 +207,13 @@ proc/arctan(x)
/*
* Tangent.
*/
-/proc/Tan(const/x)
+/proc/Tan(const/x)
return sin(x) / cos(x)
+/proc/tan_rad(const/x) // This one assumes that x is in radians.
+ return Tan(ToDegrees(x))
+
+
/proc/ToDegrees(const/radians)
// 180 / Pi
return radians * 57.2957795
diff --git a/code/datums/gamemode/dynamic/dynamic.dm b/code/datums/gamemode/dynamic/dynamic.dm
index 003493a66d7..86bd810e8da 100644
--- a/code/datums/gamemode/dynamic/dynamic.dm
+++ b/code/datums/gamemode/dynamic/dynamic.dm
@@ -9,8 +9,47 @@ var/list/threat_by_job = list(
"Detective" = 3,
)
+var/dynamic_curve_centre = 0
+var/dynamic_curve_width = 1.8
+
#define BASE_SOLO_REFUND 10
+/proc/lorentz2threat(var/x)
+ var/y
+ switch (x)
+ // Left end of the tail, the lowest bound is -inf.
+ // 0 to 10.
+ if (-INFINITY to -20)
+ y = rand(0, 10)
+ // Porportional conversion from the lorentz variable to the threa.
+
+ // First, we use a rule of three to get a number from -40 to -30.
+ // Then we shift it by 50 to get a number from 10 to 20.
+ // The same process is done for other intervalls.
+ if (-20 to -10)
+ y = RULE_OF_THREE(-40, -20, x) + 50
+ if (-10 to -5)
+ y = RULE_OF_THREE(-30, -10, x) + 50
+ if (-5 to -2.5)
+ y = RULE_OF_THREE(-20, -5, x) + 50
+ if (-2.5 to -0)
+ y = RULE_OF_THREE(-10, -2.5, x) + 50
+ if (0 to 2.5)
+ y = RULE_OF_THREE(10, 2.5, x) + 50
+ if (2.5 to 5)
+ y = RULE_OF_THREE(20, 5, x) + 50
+ if (5 to 10)
+ y = RULE_OF_THREE(30, 10, x) + 50
+ if (10 to 20)
+ y = RULE_OF_THREE(40, 20, x) + 50
+
+ // Right end of the tail, higher bound is +inf.
+
+ if (20 to INFINITY)
+ y = rand(90, 100)
+
+ return y
+
/datum/gamemode/dynamic
name = "Dynamic Mode"
@@ -43,11 +82,19 @@ var/list/threat_by_job = list(
var/datum/stat/dynamic_mode/dynamic_stats = null
var/pop_last_updated = 0
+ var/relative_threat = 0 // Relative threat, Lorentz-distributed.
+ var/curve_centre_of_round = 0
+ var/curve_width_of_round = 1.8
+
+ var/peaceful_percentage = 50
+
/datum/gamemode/dynamic/AdminPanelEntry()
var/dat = list()
dat += "Dynamic Mode \[VV\]
"
dat += "Threat Level: [threat_level]
"
dat += "Threat to Spend: [threat] \[Adjust\] \[View Log\]
"
+ dat += "Parameters: centre = [curve_centre_of_round] ; width = [curve_width_of_round].
"
+ dat += "On average, [peaceful_percentage]% of the rounds are more peaceful.
"
dat += "Executed rulesets: "
if (executed_rules.len > 0)
dat += "
"
@@ -112,13 +159,30 @@ var/list/threat_by_job = list(
send2maindiscord("Dynamic mode threat: **[threat_level]**, rulesets: [jointext(rules, ", ")]")
/datum/gamemode/dynamic/can_start()
- threat_level = rand(1,100)*0.6 + rand(1,100)*0.4//https://docs.google.com/spreadsheets/d/1QLN_OBHqeL4cm9zTLEtxlnaJHHUu0IUPzPbsI-DFFmc/edit#gid=499381388
+
+ // Old equation.
+ //threat_level = rand(1,100)*0.6 + rand(1,100)*0.4//https://docs.google.com/spreadsheets/d/1QLN_OBHqeL4cm9zTLEtxlnaJHHUu0IUPzPbsI-DFFmc/edit#gid=499381388
+
+ // New equation : https://docs.google.com/spreadsheets/d/1qnQm5hDdwZoyVmBCtf6-jwwHKEaCnYa3ljmYPs7gkSE/edit#gid=0
+ relative_threat = lorentz_distribution(dynamic_curve_centre, dynamic_curve_width)
+ threat_level = lorentz2threat(relative_threat)
+ threat = round(threat, 0.1)
+ peaceful_percentage = round(lorentz_cummulative_distribution(relative_threat, curve_centre_of_round, curve_width_of_round), 0.01)*100
+
+ curve_centre_of_round = dynamic_curve_centre
+ curve_width_of_round = dynamic_curve_width
+
threat = threat_level
starting_threat = threat_level
+
latejoin_injection_cooldown = rand(330,510)
midround_injection_cooldown = rand(600,1050)
message_admins("Dynamic Mode initialized with a Threat Level of... [threat_level]!")
log_admin("Dynamic Mode initialized with a Threat Level of... [threat_level]!")
+
+ message_admins("Parameters were: centre = [curve_centre_of_round], width = [curve_width_of_round].")
+ log_admin("Parameters were: centre = [curve_centre_of_round], width = [curve_width_of_round].")
+
if (config.high_population_override)
message_admins("High Population Override is in effect! Threat Level will have more impact on which roles will appear, and player population less.")
log_admin("High Population Override is in effect! Threat Level will have more impact on which roles will appear, and player population less.")
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 8008692a49b..8e53790d8a8 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -687,6 +687,9 @@ var/global/floorIsLava = 0
for(var/datum/dynamic_ruleset/roundstart/rule in forced_roundstart_ruleset)
dat += {"-> [rule.name] <-
"}
dat += "(Clear Rulesets)
"
+ dat += "Parameters:
"
+ dat += {"Curve centre: -> [dynamic_curve_centre] <-
"}
+ dat += {"Curve width: -> [dynamic_curve_width] <-
"}
else
dat += "(Force Next Latejoin Ruleset)
"
if (ticker && ticker.mode && istype(ticker.mode,/datum/gamemode/dynamic))
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index a8518ef11b3..439ae945096 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1557,6 +1557,39 @@
mode.picking_specific_rule(midround_rules[added_rule],1)
+ else if(href_list["f_dynamic_roundstart_centre"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(ticker && ticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(master_mode != "Dynamic Mode")
+ return alert(usr, "The game mode has to be Dynamic Mode!", null, null, null, null)
+
+ var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num
+ if (new_centre < -5 || new_centre > 5)
+ return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].")
+ message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1)
+ dynamic_curve_centre = new_centre
+
+ else if(href_list["f_dynamic_roundstart_width"])
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(ticker && ticker.mode)
+ return alert(usr, "The game has already started.", null, null, null, null)
+ if(master_mode != "Dynamic Mode")
+ return alert(usr, "The game mode has to be Dynamic Mode!", null, null, null, null)
+
+ var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num
+ if (new_width < 0.5 || new_width > 4)
+ return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null)
+
+ log_admin("[key_name(usr)] changed the distribution curve width to [new_width].")
+ message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1)
+ dynamic_curve_width = new_width
else if(href_list["c_mode2"])
if(!check_rights(R_ADMIN|R_SERVER))