summaryrefslogtreecommitdiff
path: root/revert-18-Simplify-broken-limb-mending-3054.patch
blob: dd3aa5222f7be8315265e3f8740e198ef116ecbd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
From d8aa6d9696fbce4339edfccf96df85ae438a8fad Mon Sep 17 00:00:00 2001
From: Coolthulhu <Coolthulhu@gmail.com>
Date: Thu, 21 Sep 2023 15:58:59 +0200
Subject: [PATCH] Simplify broken limb mending (#3054)

* Rework broken limbs and mending

* Redesign mending_modifier: 1.0 is same as splint

* Update comments

* Update disabled effect description

---------

Co-authored-by: Olanti <olanti-p@yandex.ru>
---
 data/json/effects.json             |  14 +---
 data/json/mutations/mutations.json |  33 ++++-----
 data/json/obsoletion/effects.json  |   4 +
 src/bionics.cpp                    |  24 ------
 src/character.cpp                  |  66 +++++++++--------
 src/character.h                    |   6 +-
 src/creature.cpp                   |   4 +-
 src/iexamine.cpp                   |  15 ++--
 src/iuse_actor.cpp                 |  12 ---
 src/mutation.h                     |   6 +-
 src/mutation_data.cpp              |   2 +-
 src/npc.cpp                        |   3 +-
 src/panels.cpp                     |  45 ++++++-----
 src/player_hardcoded_effects.cpp   |   7 +-
 src/suffer.cpp                     | 115 -----------------------------
 tests/player_helpers.cpp           |   3 +
 16 files changed, 96 insertions(+), 263 deletions(-)

diff --git a/data/json/effects.json b/data/json/effects.json
index 394bfb1a915..969cafe5046 100644
--- a/data/json/effects.json
+++ b/data/json/effects.json
@@ -1802,23 +1802,11 @@
     "max_duration": "1 s",
     "rating": "bad"
   },
-  {
-    "type": "effect_type",
-    "id": "mending",
-    "name": [ "Started recovery", "Recovering", "Mostly recovered" ],
-    "desc": [ "This damaged limb is slowly regaining its functions." ],
-    "//": "Duration is 10 days, but the actual time taken is probabilistic.",
-    "max_duration": "10 d",
-    "int_dur_factor": "60 h",
-    "max_intensity": 3,
-    "rating": "good",
-    "permanent": true
-  },
   {
     "type": "effect_type",
     "id": "disabled",
     "name": [ { "ctxt": "physically", "str": "Disabled" } ],
-    "desc": [ "This limb is damaged beyond use and may require a splint to recover." ],
+    "desc": [ "This limb is damaged beyond use and must fully heal to recover.  Using a splint may speed up the process." ],
     "//": "This sounds weird. We need <bp_affected> tag or something",
     "apply_message": "Your limb breaks!",
     "remove_message": "The broken limb has mended.",
diff --git a/data/json/mutations/mutations.json b/data/json/mutations/mutations.json
index 1fe217b8559..333a471d30e 100644
--- a/data/json/mutations/mutations.json
+++ b/data/json/mutations/mutations.json
@@ -155,7 +155,7 @@
     "category": [ "MEDICAL" ],
     "healing_awake": 0.2,
     "healing_resting": 0.5,
-    "mending_modifier": 2.0
+    "mending_modifier": 0.5
   },
   {
     "type": "mutation",
@@ -722,37 +722,34 @@
     "id": "SLOWHEALER",
     "name": { "str": "Slow Healer" },
     "points": -2,
-    "description": "Your wounds heal a little slower than most.  Your HP whilst asleep, as well as your broken limbs, heal at 75% of the regular rate.",
+    "description": "Your wounds heal a little slower than most.  Your HP whilst asleep heals at 75% of the regular rate.",
     "starting_trait": true,
     "types": [ "HEALING" ],
-    "healing_resting": -0.25,
-    "mending_modifier": 0.5
+    "healing_resting": -0.25
   },
   {
     "type": "mutation",
     "id": "SLOWHEALER2",
     "name": { "str": "Poor Healer" },
     "points": -4,
-    "description": "Your health recovery is severely impaired.  Your HP whilst asleep, as well as your broken limbs, heal at 33% of the regular rate.",
+    "description": "Your health recovery is severely impaired.  Your HP whilst asleep heals at 33% of the regular rate.",
     "starting_trait": true,
     "valid": false,
     "purifiable": false,
     "types": [ "HEALING" ],
-    "healing_resting": -0.66,
-    "mending_modifier": 0.33
+    "healing_resting": -0.66
   },
   {
     "type": "mutation",
     "id": "SLOWHEALER3",
     "name": { "str": "Imperceptive Healer" },
     "points": -8,
-    "description": "Wounds are incredibly dangerous to you, as they barely heal at all.  Your HP whilst asleep, as well as your broken limbs, heal at 10% of the regular rate.",
+    "description": "Wounds are incredibly dangerous to you, as they barely heal at all.  Your HP whilst asleep heals at 10% of the regular rate.",
     "starting_trait": true,
     "valid": false,
     "purifiable": false,
     "types": [ "HEALING" ],
-    "healing_resting": -0.9,
-    "mending_modifier": 0.1
+    "healing_resting": -0.9
   },
   {
     "type": "mutation",
@@ -1377,34 +1374,34 @@
     "category": [ "PLANT", "LIZARD" ],
     "healing_awake": 0.66,
     "healing_resting": 0.5,
-    "mending_modifier": 4.0
+    "mending_modifier": 0.5
   },
   {
     "type": "mutation",
     "id": "REGEN",
     "name": { "str": "Regeneration" },
     "points": 6,
-    "description": "Your flesh regenerates from wounds incredibly quickly.  You heal 150% faster whilst asleep and 200% faster whilst awake.  Your broken limbs also heal 16 times faster than usual.",
+    "description": "Your flesh regenerates from wounds incredibly quickly.  You heal 150% faster whilst asleep and 200% faster whilst awake.  You do not require splints to heal broken limbs.",
     "types": [ "HEALING" ],
     "prereqs": [ "FASTHEALER2" ],
     "category": [ "SLIME", "TROGLOBITE" ],
     "healing_awake": 2.0,
     "healing_resting": 1.5,
-    "mending_modifier": 16.0
+    "mending_modifier": 1.0
   },
   {
     "type": "mutation",
     "id": "REGEN_LIZ",
     "name": { "str": "Reptilian Healing" },
-    "points": 5,
+    "//": "Not worth a point, barely more than flavor.",
+    "points": 0,
     "valid": false,
     "purifiable": false,
-    "description": "Your broken limbs mend themselves without significant difficulty.  You do not require splints and broken limbs heal 20 times faster than usual.",
-    "cancels": [ "ROT1", "ROT2", "ROT3" ],
-    "prereqs": [ "FASTHEALER2" ],
+    "description": "Your broken limbs mend themselves without significant difficulty.  You do not require splints.",
+    "prereqs": [ "FASTHEALER" ],
     "threshreq": [ "THRESH_LIZARD" ],
     "category": [ "LIZARD" ],
-    "mending_modifier": 20.0
+    "mending_modifier": 1.0
   },
   {
     "type": "mutation",
diff --git a/data/json/obsoletion/effects.json b/data/json/obsoletion/effects.json
index 38c218728cd..bb666566f6b 100644
--- a/data/json/obsoletion/effects.json
+++ b/data/json/obsoletion/effects.json
@@ -60,5 +60,9 @@
   {
     "type": "effect_type",
     "id": "took_anticonvulsant_visible"
+  },
+  {
+    "type": "effect_type",
+    "id": "mending"
   }
 ]
diff --git a/src/bionics.cpp b/src/bionics.cpp
index 78827e780d7..bb2b2bd73e7 100644
--- a/src/bionics.cpp
+++ b/src/bionics.cpp
@@ -101,7 +101,6 @@ static const efftype_id effect_fungus( "fungus" );
 static const efftype_id effect_hallu( "hallu" );
 static const efftype_id effect_heating_bionic( "heating_bionic" );
 static const efftype_id effect_iodine( "iodine" );
-static const efftype_id effect_mending( "mending" );
 static const efftype_id effect_meth( "meth" );
 static const efftype_id effect_narcosis( "narcosis" );
 static const efftype_id effect_operating( "operating" );
@@ -190,7 +189,6 @@ static const trait_id trait_MASOCHIST_MED( "MASOCHIST_MED" );
 static const trait_id trait_NOPAIN( "NOPAIN" );
 static const trait_id trait_PROF_AUTODOC( "PROF_AUTODOC" );
 static const trait_id trait_PROF_MED( "PROF_MED" );
-static const trait_id trait_REGEN_LIZ( "REGEN_LIZ" );
 static const trait_id trait_THRESH_MEDICAL( "THRESH_MEDICAL" );
 
 static const std::string flag_ALLOWS_NATURAL_ATTACKS( "ALLOWS_NATURAL_ATTACKS" );
@@ -1675,18 +1673,6 @@ void Character::process_bionic( bionic &bio )
             }
             if( calendar::once_every( 2_minutes ) ) {
                 std::vector<bodypart_id> damaged_hp_parts;
-                std::vector<effect *> mending_list;
-
-                for( const bodypart_id &bp : get_all_body_parts( true ) ) {
-                    const int hp_cur = get_part_hp_cur( bp );
-                    if( !is_limb_broken( bp ) && hp_cur < get_part_hp_max( bp ) ) {
-                        damaged_hp_parts.push_back( bp );
-                    } else if( has_effect( effect_mending, bp.id() ) &&
-                               ( has_trait( trait_REGEN_LIZ ) || worn_with_flag( flag_SPLINT, bp ) ) ) {
-                        effect *e = &get_effect( effect_mending, bp->token );
-                        mending_list.push_back( e );
-                    }
-                }
                 if( !damaged_hp_parts.empty() ) {
                     // Essential parts are considered 10 HP lower than non-essential parts for the purpose of determining priority.
                     // I'd use the essential_value, but it's tied up in the heal_actor class of iuse_actor.
@@ -1703,16 +1689,6 @@ void Character::process_bionic( bionic &bio )
                         mod_stored_kcal( -bio.info().kcal_trigger );
                     }
                 }
-                if( !mending_list.empty() ) {
-                    for( effect *e : mending_list ) {
-                        if( !can_use_bionic() ) {
-                            return;
-                        }
-                        e->mod_duration( e->get_max_duration() / 100 );
-                        mod_power_level( -bio.info().power_trigger );
-                        mod_stored_kcal( -bio.info().kcal_trigger );
-                    }
-                }
             }
         }
     } else if( bio.id == bio_painkiller ) {
diff --git a/src/character.cpp b/src/character.cpp
index 6cb10461791..761e0785f44 100644
--- a/src/character.cpp
+++ b/src/character.cpp
@@ -140,6 +140,7 @@ static const efftype_id effect_cough_suppress( "cough_suppress" );
 static const efftype_id effect_crushed( "crushed" );
 static const efftype_id effect_darkness( "darkness" );
 static const efftype_id effect_deaf( "deaf" );
+static const efftype_id effect_disabled( "disabled" );
 static const efftype_id effect_disinfected( "disinfected" );
 static const efftype_id effect_downed( "downed" );
 static const efftype_id effect_drunk( "drunk" );
@@ -167,7 +168,6 @@ static const efftype_id effect_lying_down( "lying_down" );
 static const efftype_id effect_melatonin_supplements( "melatonin" );
 static const efftype_id effect_meth( "meth" );
 static const efftype_id effect_masked_scent( "masked_scent" );
-static const efftype_id effect_mending( "mending" );
 static const efftype_id effect_narcosis( "narcosis" );
 static const efftype_id effect_nausea( "nausea" );
 static const efftype_id effect_no_sight( "no_sight" );
@@ -287,7 +287,6 @@ static const trait_id trait_INFRARED( "INFRARED" );
 static const trait_id trait_LEG_TENT_BRACE( "LEG_TENT_BRACE" );
 static const trait_id trait_LIGHT_BONES( "LIGHT_BONES" );
 static const trait_id trait_LIZ_IR( "LIZ_IR" );
-static const trait_id trait_REGEN_LIZ( "REGEN_LIZ" );
 static const trait_id trait_M_DEPENDENT( "M_DEPENDENT" );
 static const trait_id trait_M_IMMUNE( "M_IMMUNE" );
 static const trait_id trait_M_SKIN2( "M_SKIN2" );
@@ -1257,14 +1256,15 @@ int Character::get_working_leg_count() const
 
 bool Character::is_limb_disabled( const bodypart_id &limb ) const
 {
-    return get_part_hp_cur( limb ) <= get_part_hp_max( limb ) * .125;
+    return is_limb_broken( limb ) ||
+           ( get_part_hp_cur( limb ) <= get_part_hp_max( limb ) * 0.125 );
 }
 
 // this is the source of truth on if a limb is broken so all code to determine
 // if a limb is broken should point here to make any future changes to breaking easier
 bool Character::is_limb_broken( const bodypart_id &limb ) const
 {
-    return get_part_hp_cur( limb ) <= 0;
+    return has_effect( effect_disabled, limb.id() );
 }
 
 bool Character::can_run()
@@ -4580,8 +4580,17 @@ void Character::regen( int rate_multiplier )
 
     float rest = rest_quality();
     float heal_rate = healing_rate( rest ) * to_turns<int>( 5_minutes );
+    const float broken_regen_mod = clamp( mutation_value( "mending_modifier" ), 0.25f, 1.0f );
     if( heal_rate > 0.0f ) {
-        healall( roll_remainder( rate_multiplier * heal_rate ) );
+        const int base_heal = roll_remainder( rate_multiplier * heal_rate );
+        const int broken_heal = roll_remainder( base_heal * broken_regen_mod );
+
+        for( const bodypart_id &bp : get_all_body_parts() ) {
+            const bool is_broken = is_limb_broken( bp ) &&
+                                   !worn_with_flag( flag_SPLINT, bp );
+            heal( bp, is_broken ? broken_heal : base_heal );
+            mod_part_healed_total( bp, is_broken ? broken_heal : base_heal );
+        }
     } else if( heal_rate < 0.0f ) {
         int rot_rate = roll_remainder( rate_multiplier * -heal_rate );
         // Has to be in loop because some effects depend on rounding
@@ -4595,9 +4604,13 @@ void Character::regen( int rate_multiplier )
         const bodypart_id &bp = convert_bp( hp_to_bp( static_cast<hp_part>( i ) ) ).id();
         float healing = healing_rate_medicine( rest, bp ) * to_turns<int>( 5_minutes );
 
-        int healing_apply = roll_remainder( healing );
+        const bool is_broken = is_limb_broken( bp ) &&
+                               !worn_with_flag( flag_SPLINT, bp );
+        const int healing_apply = roll_remainder( is_broken ? healing *broken_regen_mod : healing );
+
         healed_bp( i, healing_apply );
         heal( bp, healing_apply );
+
         if( damage_bandaged[i] > 0 ) {
             damage_bandaged[i] -= healing_apply;
             if( damage_bandaged[i] <= 0 ) {
@@ -4707,9 +4720,6 @@ void Character::update_body( const time_point &from, const time_point &to )
         check_needs_extremes();
         update_needs( five_mins );
         regen( five_mins );
-        // Note: mend ticks once per 5 minutes, but wants rate in TURNS, not 5 minute intervals
-        // TODO: change @ref med to take time_duration
-        mend( five_mins * to_turns<int>( 5_minutes ) );
     }
     if( ticks_between( from, to, 24_hours ) > 0 ) {
         enforce_minimum_healing();
@@ -5800,15 +5810,11 @@ hp_part Character::body_window( const std::string &menu_header,
         const nc_color all_state_col = limb_color( bp, true, true, true );
         // Broken means no HP can be restored, it requires surgical attention.
         const bool limb_is_broken = is_limb_broken( bp );
-        const bool limb_is_mending = limb_is_broken &&
-                                     ( worn_with_flag( flag_SPLINT, bp ) || has_trait( trait_REGEN_LIZ ) );
 
         if( show_all ) {
             e.allowed = true;
         } else if( has_curable_effect ) {
             e.allowed = true;
-        } else if( limb_is_broken ) {
-            e.allowed = false;
         } else if( current_hp < maximal_hp && ( e.bonus != 0 || bandage_power > 0.0f  ||
                                                 disinfectant_power > 0.0f ) ) {
             e.allowed = true;
@@ -5837,21 +5843,21 @@ hp_part Character::body_window( const std::string &menu_header,
 
         const auto &aligned_name = std::string( max_bp_name_len - utf8_width( e.name ), ' ' ) + e.name;
         std::string hp_str;
-        if( limb_is_mending ) {
-            desc += colorize( _( "It is broken but has been set and just needs time to heal." ),
+        if( limb_is_broken ) {
+            const nc_color color = worn_with_flag( flag_SPLINT, bp ) ||
+                                   ( mutation_value( "mending_modifier" ) >= 1.0f ) ?
+                                   c_blue :
+                                   c_light_red;
+            desc += colorize( _( "It is broken and must heal fully before it becomes functional again." ),
                               c_blue ) + "\n";
-            const auto &eff = get_effect( effect_mending, bp_token );
-            const int mend_perc = eff.is_null() ? 0.0 : 100 * eff.get_duration() / eff.get_max_duration();
+            const int mend_perc = 100 * current_hp / maximal_hp;
 
             if( precise ) {
-                hp_str = colorize( string_format( "=%2d%%=", mend_perc ), c_blue );
+                hp_str = colorize( string_format( "=%2d%%=", mend_perc ), color );
             } else {
                 const int num = mend_perc / 20;
-                hp_str = colorize( std::string( num, '#' ) + std::string( 5 - num, '=' ), c_blue );
+                hp_str = colorize( std::string( num, '#' ) + std::string( 5 - num, '=' ), color );
             }
-        } else if( limb_is_broken ) {
-            desc += colorize( _( "It is broken.  It needs a splint or surgical attention." ), c_red ) + "\n";
-            hp_str = "==%==";
         } else if( precise ) {
             hp_str = string_format( "%d", current_hp );
         } else {
@@ -8431,11 +8437,6 @@ void Character::apply_damage( Creature *source, bodypart_id hurt, int dam,
         put_into_vehicle_or_drop( *this, item_drop_reason::tumbling, { weapon } );
         i_rem( &weapon );
     }
-    if( has_effect( effect_mending, part_to_damage->token ) ) {
-        effect &e = get_effect( effect_mending, part_to_damage->token );
-        float remove_mend = dam / 20.0f;
-        e.mod_duration( -e.get_max_duration() * remove_mend );
-    }
 
     if( dam > get_painkiller() ) {
         on_hurt( source );
@@ -8646,10 +8647,13 @@ int Character::reduce_healing_effect( const efftype_id &eff_id, int remove_med,
 
 void Character::heal( const bodypart_id &healed, int dam )
 {
-    if( !is_limb_broken( healed ) ) {
-        int effective_heal = std::min( dam, get_part_hp_max( healed ) - get_part_hp_cur( healed ) );
-        mod_part_hp_cur( healed, effective_heal );
-        g->events().send<event_type::character_heals_damage>( getID(), effective_heal );
+    const int max_hp = get_part_hp_max( healed );
+    const int cur_hp = get_part_hp_cur( healed );
+    const int effective_heal = std::min( dam, max_hp - cur_hp );
+    mod_part_hp_cur( healed, effective_heal );
+    g->events().send<event_type::character_heals_damage>( getID(), effective_heal );
+    if( cur_hp + dam >= max_hp ) {
+        remove_effect( effect_disabled, healed.id() );
     }
 }
 
diff --git a/src/character.h b/src/character.h
index c3ff9d3f7eb..a84bf824865 100644
--- a/src/character.h
+++ b/src/character.h
@@ -742,10 +742,8 @@ class Character : public Creature, public visitable<Character>
         int get_working_arm_count() const;
         /** Returns the number of functioning legs */
         int get_working_leg_count() const;
-        /** Returns true if the limb is disabled(12.5% or less hp)*/
+        /** Returns true if the limb is disabled (12.5% or less hp, or broken)*/
         bool is_limb_disabled( const bodypart_id &limb ) const;
-        /** Returns true if the limb is hindered(40% or less hp) */
-        bool is_limb_hindered( hp_part limb ) const;
         /** Returns true if the limb is broken */
         bool is_limb_broken( const bodypart_id &limb ) const;
         /** source of truth of whether a Character can run */
@@ -2063,8 +2061,6 @@ class Character : public Creature, public visitable<Character>
         void suffer();
         /** Handles mitigation and application of radiation */
         bool irradiate( float rads, bool bypass = false );
-        /** Handles the chance for broken limbs to spontaneously heal to 1 HP */
-        void mend( int rate_multiplier );
 
         /** Creates an auditory hallucination */
         void sound_hallu();
diff --git a/src/creature.cpp b/src/creature.cpp
index 0d69db54329..d1c48950682 100644
--- a/src/creature.cpp
+++ b/src/creature.cpp
@@ -1652,14 +1652,14 @@ void Creature::mod_part_healed_total( const bodypart_id &id, int mod )
 void Creature::set_all_parts_hp_cur( const int set )
 {
     for( std::pair<const bodypart_str_id, bodypart> &elem : body ) {
-        elem.second.set_hp_cur( set );
+        set_part_hp_cur( elem.first, set );
     }
 }
 
 void Creature::set_all_parts_hp_to_max()
 {
     for( std::pair<const bodypart_str_id, bodypart> &elem : body ) {
-        elem.second.set_hp_to_max();
+        set_part_hp_cur( elem.first, get_part_hp_max( elem.first ) );
     }
 }
 
diff --git a/src/iexamine.cpp b/src/iexamine.cpp
index cb1af8fb5a7..5295e4bf0ca 100644
--- a/src/iexamine.cpp
+++ b/src/iexamine.cpp
@@ -123,7 +123,6 @@ static const efftype_id effect_bleed( "bleed" );
 static const efftype_id effect_disinfected( "disinfected" );
 static const efftype_id effect_earphones( "earphones" );
 static const efftype_id effect_infected( "infected" );
-static const efftype_id effect_mending( "mending" );
 static const efftype_id effect_pblue( "pblue" );
 static const efftype_id effect_pkill2( "pkill2" );
 static const efftype_id effect_sleep( "sleep" );
@@ -5081,11 +5080,7 @@ void iexamine::autodoc( player &p, const tripoint &examp )
             for( int i = 0; i < num_hp_parts; i++ ) {
                 const bodypart_id &part = convert_bp( player::hp_to_bp( static_cast<hp_part>( i ) ) ).id();
                 const bool broken = patient.is_limb_broken( part );
-                effect &existing_effect = patient.get_effect( effect_mending, part->token );
-                // Skip part if not broken or already healed 50%
-                if( !broken || ( !existing_effect.is_null() &&
-                                 existing_effect.get_duration() >
-                                 existing_effect.get_max_duration() - 5_days - 1_turns ) ) {
+                if( !broken ) {
                     continue;
                 }
                 broken_limbs_count++;
@@ -5116,9 +5111,11 @@ void iexamine::autodoc( player &p, const tripoint &examp )
                     patient.add_msg_player_or_npc( m_good, _( "The machine rapidly sets and splints your broken %s." ),
                                                    _( "The machine rapidly sets and splints <npcname>'s broken %s." ),
                                                    body_part_name( part ) );
-                    patient.add_effect( effect_mending, 0_turns, part->token );
-                    effect &mending_effect = patient.get_effect( effect_mending, part->token );
-                    mending_effect.set_duration( mending_effect.get_max_duration() - 5_days );
+                    // TODO: Prevent exploits with hp draining stuff?
+                    int heal_amt = patient.get_part_hp_max( part ) / 2 - patient.get_part_hp_cur( part );
+                    if( heal_amt > 0 ) {
+                        patient.heal( part, heal_amt );
+                    }
                 }
             }
             if( broken_limbs_count == 0 ) {
diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp
index a0d6e66b7dc..4a0eb9befdb 100644
--- a/src/iuse_actor.cpp
+++ b/src/iuse_actor.cpp
@@ -3903,18 +3903,6 @@ static hp_part pick_part_to_heal(
             return healed_part;
         }
 
-        if( patient.is_limb_broken( bp ) ) {
-            if( healed_part == hp_arm_l || healed_part == hp_arm_r ) {
-                add_msg( m_info, _( "That arm is broken.  It needs surgical attention or a splint." ) );
-            } else if( healed_part == hp_leg_l || healed_part == hp_leg_r ) {
-                add_msg( m_info, _( "That leg is broken.  It needs surgical attention or a splint." ) );
-            } else {
-                add_msg( m_info, "That body part is bugged.  It needs developer's attention." );
-            }
-
-            continue;
-        }
-
         if( force || patient.get_part_hp_cur( bp ) < patient.get_part_hp_max( bp ) ) {
             return healed_part;
         }
diff --git a/src/mutation.h b/src/mutation.h
index 8e82983e2d7..8fe226d47b5 100644
--- a/src/mutation.h
+++ b/src/mutation.h
@@ -115,8 +115,10 @@ struct mutation_branch {
         // Healing per turn
         float healing_awake = 0.0f;
         float healing_resting = 0.0f;
-        // Limb mending bonus
-        float mending_modifier = 1.0f;
+        // Multiplier on regen of broken limbs.
+        // Base regen of broken limbs is 25% and 25% the low cap.
+        // Capped at 1.0, which makes broken limbs regen at same rate as unbroken.
+        float mending_modifier = 0.0f;
         // Bonus HP multiplier. That is, 1.0 doubles hp, -0.5 halves it.
         float hp_modifier = 0.0f;
         // Second HP modifier that stacks with first but is otherwise identical.
diff --git a/src/mutation_data.cpp b/src/mutation_data.cpp
index 9c95b97873e..a070de723d8 100644
--- a/src/mutation_data.cpp
+++ b/src/mutation_data.cpp
@@ -377,7 +377,7 @@ void mutation_branch::load( const JsonObject &jo, const std::string & )
     optional( jo, was_loaded, "pain_recovery", pain_recovery, 0.0f );
     optional( jo, was_loaded, "healing_awake", healing_awake, 0.0f );
     optional( jo, was_loaded, "healing_resting", healing_resting, 0.0f );
-    optional( jo, was_loaded, "mending_modifier", mending_modifier, 1.0f );
+    optional( jo, was_loaded, "mending_modifier", mending_modifier, 0.0f );
     optional( jo, was_loaded, "hp_modifier", hp_modifier, 0.0f );
     optional( jo, was_loaded, "hp_modifier_secondary", hp_modifier_secondary, 0.0f );
     optional( jo, was_loaded, "hp_adjustment", hp_adjustment, 0.0f );
diff --git a/src/npc.cpp b/src/npc.cpp
index 36b63dd2bb4..9c31869540d 100644
--- a/src/npc.cpp
+++ b/src/npc.cpp
@@ -84,7 +84,6 @@ static const efftype_id effect_contacts( "contacts" );
 static const efftype_id effect_drunk( "drunk" );
 static const efftype_id effect_feral_killed_recently( "feral_killed_recently" );
 static const efftype_id effect_infection( "infection" );
-static const efftype_id effect_mending( "mending" );
 static const efftype_id effect_npc_flee_player( "npc_flee_player" );
 static const efftype_id effect_npc_suspend( "npc_suspend" );
 static const efftype_id effect_pkill_l( "pkill_l" );
@@ -1073,7 +1072,7 @@ bool npc::wear_if_wanted( const item &it, std::string &reason )
         for( int i = 0; i < num_hp_parts; i++ ) {
             hp_part hpp = static_cast<hp_part>( i );
             body_part bp = player::hp_to_bp( hpp );
-            if( is_limb_broken( convert_bp( bp ) ) && !has_effect( effect_mending, bp ) &&
+            if( is_limb_broken( convert_bp( bp ) ) && !worn_with_flag( flag_SPLINT, convert_bp( bp ).id() ) &&
                 it.covers( convert_bp( bp ).id() ) ) {
                 reason = _( "Thanks, I'll wear that now." );
                 return !!wear_item( it, false );
diff --git a/src/panels.cpp b/src/panels.cpp
index 41983b82ae5..9c8ae1701bc 100644
--- a/src/panels.cpp
+++ b/src/panels.cpp
@@ -61,7 +61,6 @@
 #include "vpart_position.h"
 #include "weather.h"
 
-static const trait_id trait_REGEN_LIZ( "REGEN_LIZ" );
 static const trait_id trait_SELFAWARE( "SELFAWARE" );
 static const trait_id trait_THRESH_FELINE( "THRESH_FELINE" );
 static const trait_id trait_THRESH_BIRD( "THRESH_BIRD" );
@@ -69,7 +68,7 @@ static const trait_id trait_THRESH_URSINE( "THRESH_URSINE" );
 
 static const efftype_id effect_got_checked( "got_checked" );
 
-static const std::string flag_SPLINT( "SPLINT" );
+static const flag_str_id flag_SPLINT( "SPLINT" );
 
 // constructor
 window_panel::window_panel( std::function<void( avatar &, const catacurses::window & )>
@@ -771,36 +770,36 @@ static void draw_limb_health( avatar &u, const catacurses::window &w, int limb_i
             wprintz( w, color, sym );
         }
     };
+
     const bodypart_id bp = convert_bp( avatar::hp_to_bp( static_cast<hp_part>( limb_index ) ) ).id();
+    const int hp_cur = u.get_part_hp_cur( bp );
+    const int hp_max = u.get_part_hp_max( bp );
+
+    std::optional<nc_color> color_override;
+
     if( u.is_limb_broken( bp.id() ) && ( limb_index >= hp_arm_l &&
                                          limb_index <= hp_leg_r ) ) {
         //Limb is broken
-        std::string limb = "~~%~~";
-        nc_color color = c_light_red;
-
-        if( u.worn_with_flag( flag_SPLINT,  bp ) || u.has_trait( trait_REGEN_LIZ ) ) {
-            static const efftype_id effect_mending( "mending" );
-            const auto &eff = u.get_effect( effect_mending, bp->token );
-            const int mend_perc = eff.is_null() ? 0.0 : 100 * eff.get_duration() / eff.get_max_duration();
+        const int mend_perc =  100 * hp_cur / hp_max;
+        bool splinted = u.worn_with_flag( flag_SPLINT.str(), bp ) ||
+                        ( u.mutation_value( "mending_modifier" ) >= 1.0f );
+        nc_color color = splinted ? c_blue : c_dark_gray;
 
-            if( is_self_aware || u.has_effect( effect_got_checked ) ) {
-                limb = string_format( "=%2d%%=", mend_perc );
-                color = c_blue;
-            } else {
-                const int num = mend_perc / 20;
-                print_symbol_num( w, num, "#", c_blue );
-                print_symbol_num( w, 5 - num, "=", c_blue );
-                return;
-            }
+        if( is_self_aware || u.has_effect( effect_got_checked ) ) {
+            color_override = color;
+        } else {
+            const int num = mend_perc / 20;
+            print_symbol_num( w, num, "#", color );
+            print_symbol_num( w, 5 - num, "=", color );
+            return;
         }
-
-        wprintz( w, color, limb );
-        return;
     }
 
-    const int hp_cur = u.get_part_hp_cur( bp );
-    const int hp_max = u.get_part_hp_max( bp );
+
     std::pair<std::string, nc_color> hp = get_hp_bar( hp_cur, hp_max );
+    if( color_override ) {
+        hp.second = *color_override;
+    }
 
     if( is_self_aware || u.has_effect( effect_got_checked ) ) {
         wprintz( w, hp.second, "%3d  ", hp_cur );
diff --git a/src/player_hardcoded_effects.cpp b/src/player_hardcoded_effects.cpp
index 2f74c64ccf4..88d7f83de8e 100644
--- a/src/player_hardcoded_effects.cpp
+++ b/src/player_hardcoded_effects.cpp
@@ -73,7 +73,6 @@ static const efftype_id effect_hallu( "hallu" );
 static const efftype_id effect_hot( "hot" );
 static const efftype_id effect_infected( "infected" );
 static const efftype_id effect_lying_down( "lying_down" );
-static const efftype_id effect_mending( "mending" );
 static const efftype_id effect_mutating( "mutating" );
 static const efftype_id effect_nausea( "nausea" );
 static const efftype_id effect_narcosis( "narcosis" );
@@ -1292,12 +1291,8 @@ void Character::hardcoded_effects( effect &it )
                 }
             }
         }
-    } else if( id == effect_mending ) {
-        if( !is_limb_broken( convert_bp( bp ) ) ) {
-            it.set_duration( 0_turns );
-        }
     } else if( id == effect_disabled ) {
-        if( !is_limb_broken( convert_bp( bp ) ) ) {
+        if( get_part_hp_cur( convert_bp( bp ) ) >= get_part_hp_max( convert_bp( bp ) ) ) {
             remove_effect( effect_disabled );
         }
     } else if( id == effect_panacea ) {
diff --git a/src/suffer.cpp b/src/suffer.cpp
index f752944f22b..acc6a8dc50e 100644
--- a/src/suffer.cpp
+++ b/src/suffer.cpp
@@ -95,7 +95,6 @@ static const efftype_id effect_glowy_led( "glowy_led" );
 static const efftype_id effect_hallu( "hallu" );
 static const efftype_id effect_iodine( "iodine" );
 static const efftype_id effect_masked_scent( "masked_scent" );
-static const efftype_id effect_mending( "mending" );
 static const efftype_id effect_meth( "meth" );
 static const efftype_id effect_narcosis( "narcosis" );
 static const efftype_id effect_nausea( "nausea" );
@@ -143,7 +142,6 @@ static const trait_id trait_RADIOACTIVE1( "RADIOACTIVE1" );
 static const trait_id trait_RADIOACTIVE2( "RADIOACTIVE2" );
 static const trait_id trait_RADIOACTIVE3( "RADIOACTIVE3" );
 static const trait_id trait_RADIOGENIC( "RADIOGENIC" );
-static const trait_id trait_REGEN_LIZ( "REGEN_LIZ" );
 static const trait_id trait_ROOTS3( "ROOTS3" );
 static const trait_id trait_SCHIZOPHRENIC( "SCHIZOPHRENIC" );
 static const trait_id trait_SHARKTEETH( "SHARKTEETH" );
@@ -174,16 +172,6 @@ static const std::string flag_RAD_RESIST( "RAD_RESIST" );
 static const std::string flag_SPLINT( "SPLINT" );
 static const std::string flag_SUN_GLASSES( "SUN_GLASSES" );
 
-static float addiction_scaling( float at_min, float at_max, float add_lvl )
-{
-    // Not addicted
-    if( add_lvl < MIN_ADDICTION_LEVEL ) {
-        return 1.0f;
-    }
-
-    return lerp( at_min, at_max, ( add_lvl - MIN_ADDICTION_LEVEL ) / MAX_ADDICTION_LEVEL );
-}
-
 void Character::suffer_water_damage( const mutation_branch &mdata )
 {
     for( const std::pair<const bodypart_str_id, bodypart> &elem : get_body() ) {
@@ -1669,109 +1657,6 @@ bool Character::irradiate( float rads, bool bypass )
     return false;
 }
 
-void Character::mend( int rate_multiplier )
-{
-    // Wearing splints can slowly mend a broken limb back to 1 hp.
-    bool any_broken = false;
-    for( const bodypart_id &bp : get_all_body_parts() ) {
-        if( is_limb_broken( bp ) ) {
-            any_broken = true;
-            break;
-        }
-    }
-
-    if( !any_broken ) {
-        return;
-    }
-
-    double healing_factor = 1.0;
-    // Studies have shown that alcohol and tobacco use delay fracture healing time
-    // Being under effect is 50% slowdown
-    // Being addicted but not under effect scales from 25% slowdown to 75% slowdown
-    // The improvement from being intoxicated over withdrawal is intended
-    if( has_effect( effect_cig ) ) {
-        healing_factor *= 0.5;
-    } else {
-        healing_factor *= addiction_scaling( 0.25f, 0.75f, addiction_level( add_type::CIG ) );
-    }
-
-    if( has_effect( effect_drunk ) ) {
-        healing_factor *= 0.5;
-    } else {
-        healing_factor *= addiction_scaling( 0.25f, 0.75f, addiction_level( add_type::ALCOHOL ) );
-    }
-
-    if( get_rad() > 0 && !has_trait( trait_RADIOGENIC ) ) {
-        healing_factor *= clamp( ( 1000.0f - get_rad() ) / 1000.0f, 0.0f, 1.0f );
-    }
-
-    // Bed rest speeds up mending
-    if( has_effect( effect_sleep ) ) {
-        healing_factor *= 4.0;
-    } else if( get_fatigue() > fatigue_levels::dead_tired ) {
-        // but being dead tired does not...
-        healing_factor *= 0.75;
-    } else {
-        // If not dead tired, resting without sleep also helps
-        healing_factor *= 1.0f + rest_quality();
-    }
-
-    // Being healthy helps.
-    healing_factor *= 1.0f + get_healthy() / 200.0f;
-
-    // Very hungry starts lowering the chance
-    // square rooting the value makes the numbers drop off faster when below 1
-    healing_factor *= std::sqrt( static_cast<float>( get_stored_kcal() ) / static_cast<float>
-                                 ( max_stored_kcal() ) );
-    // Similar for thirst - starts at very thirsty, drops to 0 at parched
-    healing_factor *= 1.0f - clamp( 1.0f * ( get_thirst() - thirst_levels::very_thirsty ) /
-                                    +thirst_levels::parched, 0.0f, 1.0f );
-
-    // Mutagenic healing factor!
-    bool needs_splint = true;
-
-    healing_factor *= mutation_value( "mending_modifier" );
-
-    if( has_trait( trait_REGEN_LIZ ) ) {
-        needs_splint = false;
-    }
-
-    add_msg( m_debug, "Limb mend healing factor: %.2f", healing_factor );
-    if( healing_factor <= 0.0f ) {
-        // The section below assumes positive healing rate
-        return;
-    }
-
-    for( const bodypart_id &bp : get_all_body_parts() ) {
-        const bool broken = is_limb_broken( bp );
-        if( !broken ) {
-            continue;
-        }
-
-        if( needs_splint && !worn_with_flag( flag_SPLINT, bp ) ) {
-            continue;
-        }
-
-        const time_duration dur_inc = 1_turns * roll_remainder( rate_multiplier * healing_factor );
-        auto &eff = get_effect( effect_mending, bp->token );
-        if( eff.is_null() ) {
-            add_effect( effect_mending, dur_inc, bp->token );
-            continue;
-        }
-
-        eff.set_duration( eff.get_duration() + dur_inc );
-
-        if( eff.get_duration() >= eff.get_max_duration() ) {
-            set_part_hp_cur( bp, 1 );
-            remove_effect( effect_mending, bp->token );
-            g->events().send<event_type::broken_bone_mends>( getID(), bp->token );
-            //~ %s is bodypart
-            add_msg_if_player( m_good, _( "Your %s has started to mend!" ),
-                               body_part_name( bp ) );
-        }
-    }
-}
-
 void Character::sound_hallu()
 {
     // Random 'dangerous' sound from a random direction
diff --git a/tests/player_helpers.cpp b/tests/player_helpers.cpp
index fc95f1696d8..307b9a8834f 100644
--- a/tests/player_helpers.cpp
+++ b/tests/player_helpers.cpp
@@ -98,6 +98,9 @@ void clear_character( player &dummy, bool debug_storage )
     dummy.set_stamina( dummy.get_stamina_max() );
     dummy.set_movement_mode( CMM_WALK );
 
+    // Set HP to max here and also later, for disabled/broken limbs
+    dummy.set_all_parts_hp_to_max();
+
     // Make sure we don't carry around weird effects.
     dummy.clear_effects(); // mark effects for removal
     dummy.process_effects(); // actually remove them
-- 
2.42.0