1
1
import type {
2
+ Catering ,
2
3
ExerciseState ,
3
- HealthPoints ,
4
4
PatientUpdate ,
5
- PersonnelType ,
6
- } from 'digital-fuesim-manv-shared' ;
7
- import {
8
- getElement ,
9
- healthPointsDefaults ,
10
- isAlive ,
11
- Patient ,
12
5
} from 'digital-fuesim-manv-shared' ;
13
-
14
- /**
15
- * The count of assigned personnel and material that cater for a {@link Patient}.
16
- */
17
- type Catering = { [ key in PersonnelType | 'material' ] : number } ;
6
+ import { getElement , Patient } from 'digital-fuesim-manv-shared' ;
7
+ import { cloneDeep } from 'lodash-es' ;
18
8
19
9
/**
20
10
* Apply the patient tick to the {@link state}
@@ -29,29 +19,37 @@ export function patientTick(
29
19
return (
30
20
Object . values ( state . patients )
31
21
// Only look at patients that are alive and have a position, i.e. are not in a vehicle
32
- . filter ( ( patient ) => isAlive ( patient . health ) && patient . position )
22
+ . filter (
23
+ ( patient ) =>
24
+ Patient . getVisibleStatus (
25
+ patient ,
26
+ state . configuration . pretriageEnabled ,
27
+ state . configuration . bluePatientsEnabled
28
+ ) !== 'black' && ! Patient . isInVehicle ( patient )
29
+ )
33
30
. map ( ( patient ) => {
34
31
// update the time a patient is being treated, to check for pretriage later
35
32
const treatmentTime = Patient . isTreatedByPersonnel ( patient )
36
33
? patient . treatmentTime + patientTickInterval
37
34
: patient . treatmentTime ;
38
- const nextHealthPoints = getNextPatientHealthPoints (
35
+ const newTreatment = getDedicatedResources ( state , patient ) ;
36
+ const nextStateName = getNextStateName (
39
37
patient ,
40
- getDedicatedResources ( state , patient ) ,
41
- patientTickInterval
38
+ getAverageTreatment ( patient . treatmentHistory , newTreatment )
42
39
) ;
43
- const nextStateId = getNextStateId ( patient ) ;
44
40
const nextStateTime =
45
- nextStateId === patient . currentHealthStateId
41
+ nextStateName === patient . currentHealthStateName
46
42
? patient . stateTime +
47
- patientTickInterval * patient . timeSpeed
43
+ patientTickInterval *
44
+ patient . changeSpeed *
45
+ state . configuration . globalPatientChangeSpeed
48
46
: 0 ;
49
47
return {
50
48
id : patient . id ,
51
- nextHealthPoints,
52
- nextStateId,
49
+ nextStateName,
53
50
nextStateTime,
54
51
treatmentTime,
52
+ newTreatment,
55
53
} ;
56
54
} )
57
55
) ;
@@ -87,84 +85,76 @@ function getDedicatedResources(
87
85
return cateringTypes ;
88
86
}
89
87
90
- /**
91
- * Calculate the next {@link HealthPoints} for the {@link patient}.
92
- * @param patient The {@link Patient} to calculate the {@link HealthPoints} for.
93
- * @param treatedBy The count of personnel/material catering for the {@link patient}.
94
- * @param patientTickInterval The time in ms between calls to this function.
95
- * @returns The next {@link HealthPoints} for the {@link patient}
96
- */
97
- // This is a heuristic and doesn't have to be 100% correct - the players don't see the healthPoints but only the color
98
- // This function could be as complex as we want it to be (Math.sin to get something periodic, higher polynoms...)
99
- function getNextPatientHealthPoints (
100
- patient : Patient ,
101
- treatedBy : Catering ,
102
- patientTickInterval : number
103
- ) : HealthPoints {
104
- let material = treatedBy . material ;
105
- const notarzt = treatedBy . notarzt ;
106
- const notSan = treatedBy . notSan ;
107
- const rettSan = treatedBy . rettSan ;
108
- // TODO: Sans should be able to treat patients too.
109
- const functionParameters =
110
- patient . healthStates [ patient . currentHealthStateId ] ! . functionParameters ;
111
- // To do anything the personnel needs material
112
- // TODO: But a personnel should probably be able to treat a patient a bit without material - e.g. free airways, just press something on a strongly bleeding wound, etc.
113
- // -> find a better heuristic
114
- let equippedNotarzt = Math . min ( notarzt , material ) ;
115
- material = Math . max ( material - equippedNotarzt , 0 ) ;
116
- let equippedNotSan = Math . min ( notSan , material ) ;
117
- material = Math . max ( material - equippedNotSan , 0 ) ;
118
- let equippedRettSan = Math . min ( rettSan , material ) ;
119
- // much more notarzt != much better patient
120
- equippedNotarzt = Math . log2 ( equippedNotarzt + 1 ) ;
121
- equippedNotSan = Math . log2 ( equippedNotSan + 1 ) ;
122
- equippedRettSan = Math . log2 ( equippedRettSan + 1 ) ;
123
- // TODO: some more heuristic precalculations ...
124
- // e.g. each second we lose 100 health points
125
- const changedHealthPerSecond =
126
- functionParameters . constantChange +
127
- // e.g. if we have a notarzt we gain 500 additional health points per second
128
- functionParameters . notarztModifier * equippedNotarzt +
129
- functionParameters . notSanModifier * equippedNotSan +
130
- functionParameters . rettSanModifier * equippedRettSan ;
131
-
132
- return Math . max (
133
- healthPointsDefaults . min ,
134
- Math . min (
135
- healthPointsDefaults . max ,
136
- // our current health points
137
- patient . health +
138
- ( changedHealthPerSecond / 1000 ) *
139
- patientTickInterval *
140
- patient . timeSpeed
141
- )
142
- ) ;
143
- }
144
-
145
88
/**
146
89
* Find the next {@link PatientHealthState} id for the {@link patient} by using the {@link ConditionParameters}.
147
90
* @param patient The {@link Patient} to get the next {@link PatientHealthState} id for.
148
91
* @returns The next {@link PatientHealthState} id.
149
92
*/
150
- function getNextStateId ( patient : Patient ) {
151
- const currentState = patient . healthStates [ patient . currentHealthStateId ] ! ;
93
+ function getNextStateName ( patient : Patient , dedicatedResources : Catering ) {
94
+ const currentState = patient . healthStates [ patient . currentHealthStateName ] ! ;
152
95
for ( const nextConditions of currentState . nextStateConditions ) {
153
96
if (
154
97
( nextConditions . earliestTime === undefined ||
155
98
patient . stateTime > nextConditions . earliestTime ) &&
156
99
( nextConditions . latestTime === undefined ||
157
100
patient . stateTime < nextConditions . latestTime ) &&
158
- ( nextConditions . minimumHealth === undefined ||
159
- patient . health > nextConditions . minimumHealth ) &&
160
- ( nextConditions . maximumHealth === undefined ||
161
- patient . health < nextConditions . maximumHealth ) &&
162
101
( nextConditions . isBeingTreated === undefined ||
163
102
Patient . isTreatedByPersonnel ( patient ) ===
164
- nextConditions . isBeingTreated )
103
+ nextConditions . isBeingTreated ) &&
104
+ ( nextConditions . requiredMaterialAmount === undefined ||
105
+ dedicatedResources . material >=
106
+ nextConditions . requiredMaterialAmount ) &&
107
+ ( nextConditions . requiredNotArztAmount === undefined ||
108
+ dedicatedResources . notarzt >=
109
+ nextConditions . requiredNotArztAmount ) &&
110
+ ( nextConditions . requiredNotSanAmount === undefined ||
111
+ dedicatedResources . notSan + dedicatedResources . notarzt >=
112
+ nextConditions . requiredNotSanAmount ) &&
113
+ ( nextConditions . requiredRettSanAmount === undefined ||
114
+ dedicatedResources . rettSan +
115
+ dedicatedResources . notSan +
116
+ dedicatedResources . notarzt >=
117
+ nextConditions . requiredRettSanAmount ) &&
118
+ ( nextConditions . requiredSanAmount === undefined ||
119
+ dedicatedResources . san +
120
+ dedicatedResources . rettSan +
121
+ dedicatedResources . notSan +
122
+ dedicatedResources . notarzt >=
123
+ nextConditions . requiredSanAmount )
165
124
) {
166
- return nextConditions . matchingHealthStateId ;
125
+ return nextConditions . matchingHealthStateName ;
167
126
}
168
127
}
169
- return patient . currentHealthStateId ;
128
+ return patient . currentHealthStateName ;
129
+ }
130
+
131
+ /**
132
+ * Get the average treatment for roughly the last minute, scaled to 100% from {@link requiredPercentage}
133
+ */
134
+ function getAverageTreatment (
135
+ treatmentHistory : readonly Catering [ ] ,
136
+ newTreatment : Catering ,
137
+ requiredPercentage : number = 0.8
138
+ ) {
139
+ const averageCatering : Catering = cloneDeep ( newTreatment ) ;
140
+ treatmentHistory . forEach ( ( catering , index ) => {
141
+ if ( index === 0 ) {
142
+ return ;
143
+ }
144
+ averageCatering . gf += catering . gf ;
145
+ averageCatering . material += catering . material ;
146
+ averageCatering . notarzt += catering . notarzt ;
147
+ averageCatering . notSan += catering . notSan ;
148
+ averageCatering . rettSan += catering . rettSan ;
149
+ averageCatering . san += catering . san ;
150
+ } ) ;
151
+ const modifier = requiredPercentage * treatmentHistory . length ;
152
+ averageCatering . gf = averageCatering . gf / modifier ;
153
+ averageCatering . material = averageCatering . material / modifier ;
154
+ averageCatering . notarzt = averageCatering . notarzt / modifier ;
155
+ averageCatering . notSan = averageCatering . notSan / modifier ;
156
+ averageCatering . rettSan = averageCatering . rettSan / modifier ;
157
+ averageCatering . san = averageCatering . san / modifier ;
158
+
159
+ return averageCatering ;
170
160
}
0 commit comments