freenukum 0.3.5

A clone of the 1991 DOS game Duke Nukem 1
Documentation
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
use crate::{
    actor::{
        ActParameters, ActorCreateInterface, ActorData, ActorInterface,
        ActorType, HeroTouchStartParameters, RenderParameters,
        ShotParameters, ShotProcessing,
    },
    hero::{FetchedLetter, InventoryItem},
    level::{solids::LevelSolids, tiles::LevelTiles},
    Result, ANIMATION_SODA, HALFTILE_HEIGHT, OBJECT_ACCESS_CARD,
    OBJECT_BOOT, OBJECT_BOX_BLUE, OBJECT_BOX_GREY, OBJECT_BOX_RED,
    OBJECT_CHICKEN_DOUBLE, OBJECT_CHICKEN_SINGLE, OBJECT_CLAMP,
    OBJECT_DISK, OBJECT_FLAG, OBJECT_FOOTBALL, OBJECT_GLOVE, OBJECT_GUN,
    OBJECT_JOYSTICK, OBJECT_LETTER_D, OBJECT_LETTER_E, OBJECT_LETTER_K,
    OBJECT_LETTER_U, OBJECT_NUCLEARMOLECULE, OBJECT_RADIO, TILE_HEIGHT,
    TILE_WIDTH,
};

#[derive(Debug)]
pub(crate) struct Specific {
    tile: usize,
    current_frame: usize,
    num_frames: usize,
}

impl ActorCreateInterface for Specific {
    fn create(
        general: &mut ActorData,
        _solids: &mut LevelSolids,
        _tiles: &mut LevelTiles,
    ) -> Specific {
        general.position.resize(TILE_WIDTH, TILE_HEIGHT);
        general.is_in_foreground = true;

        let (tile, num_frames) = match general.actor_type {
            ActorType::BoxRedSoda | ActorType::BoxRedChicken => {
                (OBJECT_BOX_RED, 1)
            }
            ActorType::BoxBlueFootball
            | ActorType::BoxBlueJoystick
            | ActorType::BoxBlueDisk
            | ActorType::BoxBlueBalloon
            | ActorType::BoxBlueFlag
            | ActorType::BoxBlueRadio => (OBJECT_BOX_BLUE, 1),
            ActorType::BoxGreyEmpty
            | ActorType::BoxGreyBoots
            | ActorType::BoxGreyClamps
            | ActorType::BoxGreyGun
            | ActorType::BoxGreyBomb
            | ActorType::BoxGreyGlove
            | ActorType::BoxGreyFullLife
            | ActorType::BoxGreyAccessCard
            | ActorType::BoxGreyLetterD
            | ActorType::BoxGreyLetterU
            | ActorType::BoxGreyLetterK
            | ActorType::BoxGreyLetterE => (OBJECT_BOX_GREY, 1),
            ActorType::Joystick => (OBJECT_JOYSTICK, 1),
            ActorType::Football => (OBJECT_FOOTBALL, 1),
            ActorType::Flag => (OBJECT_FLAG, 3),
            ActorType::Disk => (OBJECT_DISK, 1),
            ActorType::Radio => (OBJECT_RADIO, 3),
            ActorType::Soda => (ANIMATION_SODA, 4),
            ActorType::Boots => (OBJECT_BOOT, 1),
            ActorType::Gun => (OBJECT_GUN, 1),
            ActorType::FullLife => (OBJECT_NUCLEARMOLECULE, 8),
            ActorType::ChickenSingle => (OBJECT_CHICKEN_SINGLE, 1),
            ActorType::ChickenDouble => (OBJECT_CHICKEN_DOUBLE, 1),
            ActorType::LetterD => (OBJECT_LETTER_D, 1),
            ActorType::LetterU => (OBJECT_LETTER_U, 1),
            ActorType::LetterK => (OBJECT_LETTER_K, 1),
            ActorType::LetterE => (OBJECT_LETTER_E, 1),
            ActorType::AccessCard => (OBJECT_ACCESS_CARD, 1),
            ActorType::Glove => (OBJECT_GLOVE, 1),
            ActorType::Clamps => (OBJECT_CLAMP, 1),
            _ => unreachable!(
                "Attempted to load actor type {:?} as item",
                general.actor_type
            ),
        };

        Specific {
            tile,
            current_frame: 0,
            num_frames,
        }
    }
}

impl ActorInterface for Specific {
    fn hero_touch_start(&mut self, p: HeroTouchStartParameters) {
        match p.general.actor_type {
            ActorType::LetterD => {
                p.general.is_alive = false;
                p.hero_data.fetched_letter_state.picked(FetchedLetter::D);
                p.hero_data.score.add(500);
                p.actor_adder.add_actor(
                    ActorType::Score500,
                    p.general.position.top_left(),
                );
            }
            ActorType::LetterU => {
                p.general.is_alive = false;
                p.hero_data.fetched_letter_state.picked(FetchedLetter::U);
                p.hero_data.score.add(500);
                p.actor_adder.add_actor(
                    ActorType::Score500,
                    p.general.position.top_left(),
                );
            }
            ActorType::LetterK => {
                p.general.is_alive = false;
                p.hero_data.fetched_letter_state.picked(FetchedLetter::K);
                p.hero_data.score.add(500);
                p.actor_adder.add_actor(
                    ActorType::Score500,
                    p.general.position.top_left(),
                );
            }
            ActorType::LetterE => {
                p.general.is_alive = false;
                p.hero_data.fetched_letter_state.picked(FetchedLetter::E);
                p.hero_data.score.add(500);
                if p.hero_data.fetched_letter_state.succeeded() {
                    p.actor_adder.add_actor(
                        ActorType::Score10000,
                        p.general.position.top_left(),
                    );
                    p.hero_data.score.add(10000);
                } else {
                    p.actor_adder.add_actor(
                        ActorType::Score500,
                        p.general.position.top_left(),
                    );
                    p.hero_data.score.add(500);
                }
            }
            ActorType::FullLife => {
                p.hero_data.health.fill_max();
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Gun => {
                p.hero_data.firepower.increase(1);
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::AccessCard => {
                p.hero_data.inventory.set(InventoryItem::AccessCard);
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Glove => {
                p.hero_data.inventory.set(InventoryItem::Glove);
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Boots => {
                p.hero_data.inventory.set(InventoryItem::Boot);
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Clamps => {
                p.hero_data.inventory.set(InventoryItem::Clamp);
                p.general.is_alive = false;
                p.hero_data.score.add(1000);
                p.actor_adder.add_actor(
                    ActorType::Score1000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Football => {
                p.general.is_alive = false;
                p.hero_data.score.add(100);
                p.actor_adder.add_actor(
                    ActorType::Score100,
                    p.general.position.top_left(),
                );
            }
            ActorType::Disk => {
                p.general.is_alive = false;
                p.hero_data.score.add(5000);
                p.actor_adder.add_actor(
                    ActorType::Score5000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Joystick => {
                p.general.is_alive = false;
                p.hero_data.score.add(2000);
                p.actor_adder.add_actor(
                    ActorType::Score2000,
                    p.general.position.top_left(),
                );
            }
            ActorType::Radio | ActorType::Flag => {
                p.general.is_alive = false;
                match self.current_frame {
                    0 => {
                        p.hero_data.score.add(100);
                        p.actor_adder.add_actor(
                            ActorType::Score100,
                            p.general.position.top_left(),
                        );
                    }
                    1 => {
                        p.hero_data.score.add(2000);
                        p.actor_adder.add_actor(
                            ActorType::Score2000,
                            p.general.position.top_left(),
                        );
                    }
                    2 => {
                        p.hero_data.score.add(5000);
                        p.actor_adder.add_actor(
                            ActorType::Score5000,
                            p.general.position.top_left(),
                        );
                    }
                    _ => unreachable!(),
                }
            }
            ActorType::Soda => {
                p.hero_data.health.increase(1);
                p.general.is_alive = false;
                p.hero_data.score.add(200);
                p.actor_adder.add_actor(
                    ActorType::Score200,
                    p.general.position.top_left(),
                );
            }
            ActorType::ChickenSingle => {
                p.hero_data.health.increase(1);
                p.general.is_alive = false;
                p.hero_data.score.add(100);
                p.actor_adder.add_actor(
                    ActorType::Score100,
                    p.general.position.top_left(),
                );
            }
            ActorType::ChickenDouble => {
                p.hero_data.health.increase(2);
                p.general.is_alive = false;
                p.hero_data.score.add(200);
                p.actor_adder.add_actor(
                    ActorType::Score200,
                    p.general.position.top_left(),
                );
            }
            _ => {}
        }
    }

    fn act(&mut self, p: ActParameters) {
        self.current_frame += 1;
        self.current_frame %= self.num_frames;

        if !p.solids.get(
            p.general.position.x() as u32 / TILE_WIDTH,
            p.general.position.y() as u32 / TILE_HEIGHT + 1,
        ) {
            // fall down until the actor lands on solid ground
            p.general.position.offset(0, HALFTILE_HEIGHT as i32);
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        p.renderer.place_tile(
            self.tile + self.current_frame,
            p.general.position.top_left(),
        )?;
        Ok(())
    }

    fn can_get_shot(&self, general: &ActorData) -> bool {
        matches!(
            general.actor_type,
            ActorType::BoxBlueFootball
                | ActorType::BoxBlueJoystick
                | ActorType::BoxBlueDisk
                | ActorType::BoxBlueBalloon
                | ActorType::BoxBlueFlag
                | ActorType::BoxBlueRadio
                | ActorType::BoxRedSoda
                | ActorType::BoxRedChicken
                | ActorType::BoxGreyEmpty
                | ActorType::BoxGreyBoots
                | ActorType::BoxGreyClamps
                | ActorType::BoxGreyGun
                | ActorType::BoxGreyBomb
                | ActorType::BoxGreyGlove
                | ActorType::BoxGreyFullLife
                | ActorType::BoxGreyAccessCard
                | ActorType::BoxGreyLetterD
                | ActorType::BoxGreyLetterU
                | ActorType::BoxGreyLetterK
                | ActorType::BoxGreyLetterE
                | ActorType::ChickenSingle
                | ActorType::Soda
        )
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        let pos = p.general.position.top_left();
        match p.general.actor_type {
            ActorType::BoxBlueFootball => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Football, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxBlueJoystick => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Joystick, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxBlueDisk => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Disk, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxBlueBalloon => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(
                    ActorType::Balloon,
                    pos.offset(0, -(TILE_HEIGHT as i32)),
                );
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxBlueFlag => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Flag, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxBlueRadio => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Radio, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxRedSoda => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Soda, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxRedChicken => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::ChickenSingle, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyEmpty => {
                p.general.is_alive = false;
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyBoots => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Boots, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyClamps => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Clamps, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyGun => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Gun, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyBomb => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Bomb, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyGlove => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::Glove, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyFullLife => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::FullLife, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyAccessCard => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::AccessCard, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyLetterD => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::LetterD, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyLetterU => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::LetterU, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyLetterK => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::LetterK, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::BoxGreyLetterE => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::LetterE, pos);
                p.actor_adder.add_particle_firework(pos, 4);
                ShotProcessing::Absorb
            }
            ActorType::ChickenSingle => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::ChickenDouble, pos);
                ShotProcessing::Absorb
            }
            ActorType::Soda => {
                p.general.is_alive = false;
                p.actor_adder.add_actor(ActorType::SodaFlying, pos);
                ShotProcessing::Absorb
            }
            _ => ShotProcessing::Ignore,
        }
    }
}