an http based roguelike rpg about jelqing
Commit 4b1f5f88827d
tarball11 files changed+84-35
@@ -1,6 +1,7 @@
11
pub const FRICTION_MAX: i32 = 10;22
pub const FRICTION_DRY: i32 = 2;33
pub const FRICTION_SPIT: i32 = 1;4
+pub const FRICTION_COLD: i32 = 2;45
pub const FRICTION_GRIP: i32 = 1;56
pub const FRICTION_HOLD: i32 = 2;67
pub const FRICTION_MEDITATE: i32 = 2;@@ -2,7 +2,8 @@ pub const RUPTURE_IN_BAND: u32 = 250;
22
pub const RUPTURE_WARM: u32 = 25;33
pub const RUPTURE_HOT: u32 = 10;44
pub const RUPTURE_PREMATURE: u32 = 3;5
-pub const DEGLOVING: u32 = 60;5
+pub const DEGLOVING: u32 = 20;6
+pub const COLD_DEGLOVING: u32 = 20;67
pub const DEGLOVING_INTENSITY: i32 = 8;78
pub const WINDOW: u32 = 60;89
pub const PULLED_MUSCLE: u32 = 12;@@ -25,14 +25,6 @@ impl Run {
2525
"uli is discussed on the forum, in a thread from 2009, by a man who never posted again. You have not found the reply.",2626
));2727
}28
- if !self.body.warmed_up {29
- return Err(Problem::new(30
- StatusCode::PRECONDITION_FAILED,31
- "cold-start",32
- "Warm-up was skipped. The server has noted this.",33
- )34
- .with("technique", Utility::Warmup.name()));35
- }3628
if matches!(technique, Technique::Training(_)) {3729
self.grip_closes(self.body.grip)?;3830
}@@ -103,7 +95,7 @@ impl Run {
10395
.filter(move |_| !blocked)10496
.filter(|&technique| match technique {10597
Technique::Utility(Utility::Warmup) => {106
- !self.body.warmed_up || self.here.kitchen.kettle_boiled98
+ !self.here.towel_warmed || self.here.kitchen.kettle_boiled10799
}108100
Technique::Utility(Utility::Ice) => self.items.stores.ice_packs > 0,109101
Technique::Utility(Utility::Supplement) => self.items.stores.supplements > 0,@@ -128,6 +128,7 @@ pub struct FloorState {
128128
pub(crate) temp_length: f64,129129
pub(crate) teapot: bool,130130
pub(crate) towel_stolen: bool,131
+ pub(crate) towel_warmed: bool,131132
pub(crate) meditated: bool,132133
pub(crate) brother_told: bool,133134
pub(crate) shopping: bool,@@ -6,9 +6,10 @@ use serde_json::json;
66
use super::{77
Cause, Floor, Notice, Outcome, Run,88
balance::{9
- BURN_RUPTURE, COLD_EQ, CURVE_MISSES, DANGER_EQ, DEGLOVING, DEGLOVING_INTENSITY, EQ_BAND,10
- FRIEND, HOT_EQ, LOST_IT_EQ, PREMATURE_EQ, RUPTURE_HOT, RUPTURE_IN_BAND, RUPTURE_PREMATURE,11
- RUPTURE_WARM, SOFT_INTENSITY, SUSPICION_LIMIT, WANDERER_VISIT, WINDOW,9
+ BURN_RUPTURE, COLD_DEGLOVING, COLD_EQ, CURVE_MISSES, DANGER_EQ, DEGLOVING,10
+ DEGLOVING_INTENSITY, EQ_BAND, FRIEND, HOT_EQ, LOST_IT_EQ, PREMATURE_EQ, RUPTURE_HOT,11
+ RUPTURE_IN_BAND, RUPTURE_PREMATURE, RUPTURE_WARM, SOFT_INTENSITY, SUSPICION_LIMIT,12
+ WANDERER_VISIT, WINDOW,1213
},1314
techniques::Training,1415
travel::Exit,@@ -40,6 +41,17 @@ impl Run {
4041
}4142
4243
pub(super) fn mishaps(&mut self, training: Training, intensity: i32) -> Interruption {44
+ if !self.body.warmed_up {45
+ if !self.notices.contains(&Notice::ColdStart) {46
+ self.notices.push(Notice::ColdStart);47
+ }48
+ if self.chance(49
+ self.decision("cold-degloving", "Cold and unprepared, does the skin tear?"),50
+ COLD_DEGLOVING,51
+ ) {52
+ return Err(self.end(Cause::Degloved));53
+ }54
+ }4355
if training == Training::Dry4456
&& intensity >= DEGLOVING_INTENSITY4557
&& self.chance(@@ -12,6 +12,7 @@ pub enum Notice {
1212
ThreadHasReplies,1313
Reflection,1414
MumSatisfied,15
+ ColdStart,1516
}1617
1718
impl Notice {@@ -28,6 +29,7 @@ impl Notice {
2829
Self::ThreadHasReplies => "Somewhere, a forum thread from 2009 still has replies.",2930
Self::Reflection => "the count reached ten, and then the reflection started",3031
Self::MumSatisfied => "Mum seems satisfied. She takes the washing with her.",32
+ Self::ColdStart => "Warm-up was skipped. The server has noted this.",3133
}3234
}3335
}@@ -2,10 +2,10 @@ use super::{
22
Floor, Lube, MumVisit, Notice, Observer, Run, Story,33
balance::{44
BRANCH_TURN, CAT_TAKES_TOWEL, CHANGING_ROOM_CHILL, EQ_WOBBLE, EXTENDER_GAIN,5
- FORESKIN_GLIDE, FRICTION_BURN, FRICTION_BURN_AT, FRICTION_DRY, FRICTION_GRIP, FRICTION_MAX,6
- FRICTION_SPIT, GIRTH_THICK, HOT_EQ, OBSERVED, PRIAPISM_EQ, PRIAPISM_TURNS, PULLED_MUSCLE,7
- PULLED_MUSCLE_INTENSITY, RING_PRIAPISM, RING_TIGHT_PRIAPISM, STROKE_EQ, STROKE_EQ_COST,8
- SUSPICION_LIMIT, VIGOUR,5
+ FORESKIN_GLIDE, FRICTION_BURN, FRICTION_BURN_AT, FRICTION_COLD, FRICTION_DRY,6
+ FRICTION_GRIP, FRICTION_MAX, FRICTION_SPIT, GIRTH_THICK, HOT_EQ, OBSERVED, PRIAPISM_EQ,7
+ PRIAPISM_TURNS, PULLED_MUSCLE, PULLED_MUSCLE_INTENSITY, RING_PRIAPISM, RING_TIGHT_PRIAPISM,8
+ STROKE_EQ, STROKE_EQ_COST, SUSPICION_LIMIT, VIGOUR,99
},1010
};1111
@@ -31,6 +31,9 @@ impl Run {
3131
{3232
friction -= FRICTION_GRIP;3333
}34
+ if !self.body.warmed_up {35
+ friction += FRICTION_COLD;36
+ }3437
self.body.jelq_grip = Some(self.body.grip);3538
self.body.friction = (self.body.friction + friction).min(FRICTION_MAX);3639
if self.body.friction >= FRICTION_BURN_AT@@ -19,11 +19,11 @@ const WEEK: Duration = Duration::from_secs(7 * 24 * 60 * 60);
1919
2020
impl Run {2121
pub(super) fn warm_up(&mut self) -> Result<Outcome, Problem> {22
- if self.body.warmed_up && !self.here.kitchen.kettle_boiled {22
+ if self.here.towel_warmed && !self.here.kitchen.kettle_boiled {2323
return Err(Problem::new(2424
StatusCode::CONFLICT,2525
"towel-cold",26
- "The towel has gone cold. It warms once per floor, or once after the refractory period, unless someone boils the kettle.",26
+ "The towel has gone cold. It warms once per floor, unless someone boils the kettle.",2727
));2828
}2929
if self.floor == Floor::Hotel@@ -57,9 +57,10 @@ impl Run {
5757
};5858
self.touch();5959
self.step_away();60
- if self.body.warmed_up {60
+ if self.here.towel_warmed {6161
self.here.kitchen.kettle_boiled = false;6262
}63
+ self.here.towel_warmed = true;6364
self.body.warmed_up = true;6465
self.body.eq = (self.body.eq + boost).min(100);6566
Ok(Outcome::json(StatusCode::OK, json!({ "detail": detail })))@@ -28,14 +28,21 @@ async fn bodies_do_not_repeat_the_headers() {
2828
}2929
3030
#[tokio::test]31
-async fn jelqing_cold_fails_the_precondition() {32
- let Harness { app, .. } = harness(Floor::Bedroom, SEED, |_| {});33
- let response = send(&app, "JELQ", "/runs/cafebabe?technique=stretch", &[], "").await;34
- assert_eq!(response.status(), StatusCode::PRECONDITION_FAILED);35
- assert_eq!(36
- header(&response, "content-type"),37
- "application/problem+json"38
- );31
+async fn jelqing_cold_goes_through_at_a_price() {32
+ let friction = async |warm: bool| {33
+ let app = seeded_with(Floor::Bedroom, |run| {34
+ run.body.warmed_up = warm;35
+ run.body.foreskin = Some(1.0);36
+ });37
+ let response = send(&app, "JELQ", "/runs/cafebabe?technique=stretch", &[], "").await;38
+ let noted = warnings(&response)39
+ .iter()40
+ .any(|w| w.contains("Warm-up was skipped"));41
+ assert_eq!(noted, !warm);42
+ header(&response, "x-friction").to_owned()43
+ };44
+ assert_eq!(friction(true).await, "1/10");45
+ assert_eq!(friction(false).await, "3/10");3946
}4047
4148
#[tokio::test]@@ -119,7 +126,10 @@ async fn http_1_0_gets_the_pdf_treatment() {
119126
async fn links_offer_only_usable_techniques() {120127
let app = seeded_with(Floor::Bedroom, |run| run.body.foreskin = Some(1.0));121128
let cold = send(&app, "GET", "/runs/cafebabe", &[], "").await;122
- assert_eq!(advertised(&cold), ["warmup", "ice"]);129
+ assert!(130
+ advertised(&cold).contains(&"stretch".to_owned()),131
+ "a cold jelq is allowed"132
+ );123133
let warm = send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;124134
let usable = advertised(&warm);125135
assert!(usable.contains(&"wet".to_owned()) && usable.contains(&"dry".to_owned()));@@ -160,7 +160,7 @@ async fn the_kitchen_rolls_the_normal_tiers() {
160160
}161161
162162
#[tokio::test]163
-async fn degloving_takes_one_hard_dry_stroke_in_sixty() {163
+async fn degloving_takes_one_hard_dry_stroke_in_twenty() {164164
let mut degloved = 0;165165
for seed in 0..1200u64 {166166
let app = app_with(Floor::Hotel, seed, |run| {@@ -187,7 +187,32 @@ async fn degloving_takes_one_hard_dry_stroke_in_sixty() {
187187
}188188
}189189
assert!(190
- (8..=35).contains(°loved),191
- "{degloved} degloved in 1200 hard dry strokes, about 20 expected"190
+ (35..=90).contains(°loved),191
+ "{degloved} degloved in 1200 hard dry strokes, about 60 expected"192
+ );193
+}194
+195
+#[tokio::test]196
+async fn degloving_takes_one_cold_stroke_in_twenty() {197
+ let mut degloved = 0;198
+ for seed in 0..1200u64 {199
+ let app = app_with(Floor::Hotel, seed, |run| {200
+ run.here.wifi_accepted = true;201
+ run.body.eq = 55;202
+ run.body.foreskin = Some(1.0);203
+ });204
+ let cold = send(&app, "JELQ", "/runs/cafebabe?technique=stretch", &[], "").await;205
+ if cold.status() == StatusCode::GONE206
+ && json(cold).await["type"]207
+ .as_str()208
+ .unwrap()209
+ .ends_with("degloved")210
+ {211
+ degloved += 1;212
+ }213
+ }214
+ assert!(215
+ (35..=90).contains(°loved),216
+ "{degloved} degloved in 1200 cold strokes, about 60 expected"192217
);193218
}@@ -57,7 +57,7 @@ async fn the_towel_warms_once_until_the_kettle_boils() {
5757
assert_eq!(again.status(), StatusCode::CONFLICT);5858
5959
let app = seeded_with(Floor::Bedroom, |run| {60
- run.body.warmed_up = true;60
+ run.here.towel_warmed = true;6161
run.here.kitchen.kettle_boiled = true;6262
});6363
let reheated = send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;@@ -71,6 +71,7 @@ async fn the_refractory_period_is_the_next_hold() {
7171
let app = seeded_with(Floor::Hotel, |run| {7272
run.here.wifi_accepted = true;7373
run.body.warmed_up = true;74
+ run.here.towel_warmed = true;7475
run.body.eq = 60;7576
run.body.stamina = 0;7677
run.body.exhausted = true;@@ -80,8 +81,8 @@ async fn the_refractory_period_is_the_next_hold() {
8081
let hold = send(&app, "HOLD", "/runs/cafebabe", &[], "").await;8182
assert_eq!(hold.status(), StatusCode::OK);8283
assert_eq!(header(&hold, "x-stamina"), "10/10");83
- let cold = send(&app, "JELQ", "/runs/cafebabe?technique=edge", &[], "").await;84
- assert_eq!(cold.status(), StatusCode::PRECONDITION_FAILED);84
+ let towel = send(&app, "JELQ", "/runs/cafebabe?technique=warmup", &[], "").await;85
+ assert_eq!(towel.status(), StatusCode::CONFLICT, "the towel stays cold");8586
}8687
8788
#[tokio::test]