Class: Doom::Game::Combat

Inherits:
Object
  • Object
show all
Defined in:
lib/doom/game/combat.rb

Overview

Hitscan weapon firing and monster state tracking. Matches Chocolate Doom’s P_LineAttack / P_AimLineAttack from p_map.c.

Defined Under Namespace

Classes: Projectile

Constant Summary collapse

MONSTER_HP =

Monster starting HP (from mobjinfo[] in info.c)

{
  3004 => 20,   # Zombieman
  9    => 30,   # Shotgun Guy
  3001 => 60,   # Imp
  3002 => 150,  # Demon
  58   => 150,  # Spectre
  3003 => 1000, # Baron of Hell
  69   => 500,  # Hell Knight
  3005 => 400,  # Cacodemon
  3006 => 100,  # Lost Soul
  16   => 4000, # Cyberdemon
  7    => 3000, # Spider Mastermind
  65   => 70,   # Heavy Weapon Dude
  64   => 700,  # Archvile
  71   => 400,  # Pain Elemental
  84   => 20,   # Wolfenstein SS
  2035 => 20,   # Explosive barrel
}.freeze
MONSTER_RADIUS =
{
  3004 => 20, 9 => 20, 3001 => 20, 3002 => 30, 58 => 30,
  3003 => 24, 69 => 24, 3005 => 31, 3006 => 16, 16 => 40,
  7 => 128, 65 => 20, 64 => 20, 71 => 31, 84 => 20,
  2035 => 10,  # Barrel
}.freeze
BARREL_TYPE =

Barrel (explosive, not a monster but damageable)

2035
BARREL_HP =
20
BARREL_SPLASH_RADIUS =
128.0
BARREL_SPLASH_DAMAGE =
128
DEATH_FRAMES =

Normal death frame sequences per sprite prefix (rotation 0 only) Identified by sprite heights: frames go from standing height to flat on ground

{
  'POSS' => %w[H I J K L],       # Zombieman: 55→46→34→27→17
  'SPOS' => %w[H I J K L],       # Shotgun Guy: 60→50→35→27→17
  'TROO' => %w[I J K L M],       # Imp: 62→59→54→46→22
  'SARG' => %w[I J K L M N],     # Demon/Spectre: 56→56→53→57→46→32
  'BOSS' => %w[H I J K L M N],   # Baron
  'BOS2' => %w[H I J K L M N],   # Hell Knight
  'HEAD' => %w[G H I J K L],     # Cacodemon
  'SKUL' => %w[G H I J K],       # Lost Soul
  'CYBR' => %w[I J],             # Cyberdemon
  'SPID' => %w[I J K],           # Spider Mastermind
  'CPOS' => %w[H I J K L M N],   # Heavy Weapon Dude
  'PAIN' => %w[H I J K L M],     # Pain Elemental
  'SSWV' => %w[I J K L M],       # Wolfenstein SS
  'BEXP' => %w[A B C D E],       # Barrel explosion
}.freeze
DEATH_ANIM_TICS =

Tics per death frame

6
PAIN_CHANCE =

Pain chance per monster (out of 256, from mobjinfo)

{
  3004 => 200, 9 => 170, 3001 => 200, 3002 => 180, 58 => 180,
  3003 => 50, 69 => 50, 3005 => 128, 3006 => 256, 16 => 40,
  7 => 40, 65 => 170, 64 => 10, 71 => 128, 84 => 170,
}.freeze
PAIN_DURATION =

Tics monster is stunned when in pain

6
ROCKET_SPEED =

Projectile constants

20.0
ROCKET_DAMAGE =

Map units per tic (matches DOOM’s mobjinfo MISSILESPEED)

20
ROCKET_RADIUS =

Direct hit base (DOOM: 1d8 * 20)

11
SPLASH_RADIUS =

Collision radius

128.0
SPLASH_DAMAGE =

Splash damage radius

128
MONSTER_PROJECTILES =

Monster projectile definitions

{
  imp:    { sprite: 'BAL1', speed: 10.0, damage: [3, 24], radius: 6, splash: false },
  baron:  { sprite: 'BAL7', speed: 15.0, damage: [8, 64], radius: 6, splash: false },
  caco:   { sprite: 'BAL2', speed: 10.0, damage: [5, 40], radius: 6, splash: false },
}.freeze
MONSTER_PROJECTILE_TYPE =

Map monster type to projectile type

{
  3001 => :imp,     # Imp
  3003 => :baron,   # Baron
  69   => :baron,   # Hell Knight
  3005 => :caco,    # Cacodemon
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(map, player_state, sprites, hidden_things = {}, sound_engine = nil) ⇒ Combat

Weapon damage: DOOM does (P_Random()%3 + 1) * multiplier Pistol/chaingun: 1*5..3*5 = 5-15 per bullet Shotgun: 7 pellets, each 1*5..3*5 = 5-15 Fist/chainsaw: 1*2..3*2 = 2-10



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/doom/game/combat.rb', line 99

def initialize(map, player_state, sprites, hidden_things = {}, sound_engine = nil)
  @map = map
  @player = player_state
  @sprites = sprites
  @hidden_things = hidden_things
  @sound = sound_engine
  @monster_hp = {}     # thing_idx => current HP
  @dead_things = {}    # thing_idx => { tic: death_start_tic, prefix: sprite_prefix }
  @pain_until = {}     # thing_idx => tic when pain ends
  @projectiles = []    # Active projectiles in flight
  @explosions = []     # Active explosions (for rendering)
  @puffs = []          # Bullet puff effects
  @player_x = 0.0
  @player_y = 0.0
  @player_z = 0.0
  @tic = 0
end

Instance Attribute Details

#dead_thingsObject (readonly)

Returns the value of attribute dead_things.



155
156
157
# File 'lib/doom/game/combat.rb', line 155

def dead_things
  @dead_things
end

#explosionsObject (readonly)

Returns the value of attribute explosions.



155
156
157
# File 'lib/doom/game/combat.rb', line 155

def explosions
  @explosions
end

#projectilesObject (readonly)

Returns the value of attribute projectiles.



155
156
157
# File 'lib/doom/game/combat.rb', line 155

def projectiles
  @projectiles
end

#puffsObject (readonly)

Returns the value of attribute puffs.



155
156
157
# File 'lib/doom/game/combat.rb', line 155

def puffs
  @puffs
end

Instance Method Details

#dead?(thing_idx) ⇒ Boolean

Returns:

  • (Boolean)


161
162
163
# File 'lib/doom/game/combat.rb', line 161

def dead?(thing_idx)
  @dead_things.key?(thing_idx)
end

#death_sprite(thing_idx, thing_type, viewer_angle, thing_angle) ⇒ Object

Get the current death frame sprite for a dead monster/barrel



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
# File 'lib/doom/game/combat.rb', line 166

def death_sprite(thing_idx, thing_type, viewer_angle, thing_angle)
  info = @dead_things[thing_idx]
  return nil unless info

  prefix = info[:prefix]
  frames = DEATH_FRAMES[prefix]
  return nil unless frames

  elapsed = @tic - info[:tic]
  frame_idx = elapsed / DEATH_ANIM_TICS

  # Barrels disappear after explosion animation (S_NULL in Chocolate Doom)
  if thing_type == BARREL_TYPE && frame_idx >= frames.size
    return nil
  end

  frame_idx = frame_idx.clamp(0, frames.size - 1)
  frame_letter = frames[frame_idx]

  # Use prefix directly if it differs from the thing's sprite (e.g. BEXP for barrels)
  thing_prefix = @sprites.prefix_for(thing_type)
  if prefix != thing_prefix
    @sprites.get_frame_by_prefix(prefix, frame_letter)
  else
    @sprites.get_frame(thing_type, frame_letter, viewer_angle, thing_angle)
  end
end

#fire(px, py, pz, cos_a, sin_a, weapon) ⇒ Object

Fire the current weapon



203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/doom/game/combat.rb', line 203

def fire(px, py, pz, cos_a, sin_a, weapon)
  case weapon
  when PlayerState::WEAPON_PISTOL, PlayerState::WEAPON_CHAINGUN
    hitscan(px, py, cos_a, sin_a, 1, 0.0, 5)
  when PlayerState::WEAPON_SHOTGUN
    hitscan(px, py, cos_a, sin_a, 7, Math::PI / 32, 5)
  when PlayerState::WEAPON_ROCKET
    spawn_rocket(px, py, pz, cos_a, sin_a)
  when PlayerState::WEAPON_FIST
    melee(px, py, cos_a, sin_a, 2, 64)
  when PlayerState::WEAPON_CHAINSAW
    melee(px, py, cos_a, sin_a, 2, 64)
  end
end

#in_pain?(thing_idx) ⇒ Boolean

Returns:

  • (Boolean)


157
158
159
# File 'lib/doom/game/combat.rb', line 157

def in_pain?(thing_idx)
  @pain_until[thing_idx] && @tic < @pain_until[thing_idx]
end

#spawn_monster_projectile(monster_x, monster_y, monster_z, monster_type, damage_multiplier) ⇒ Object

Spawn a monster projectile (fireball, etc.) Matches Chocolate Doom’s P_SpawnMissile: calculates momz for vertical aim



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
# File 'lib/doom/game/combat.rb', line 125

def spawn_monster_projectile(monster_x, monster_y, monster_z, monster_type, damage_multiplier)
  proj_type = MONSTER_PROJECTILE_TYPE[monster_type]
  return unless proj_type

  info = MONSTER_PROJECTILES[proj_type]
  return unless info

  dx = @player_x - monster_x
  dy = @player_y - monster_y
  dist = Math.sqrt(dx * dx + dy * dy)
  return if dist < 1

  # Normalize direction and apply speed
  speed = info[:speed]
  ndx = dx / dist * speed
  ndy = dy / dist * speed

  # P_SpawnMissile: momz = (target.z - source.z) / (dist / speed)
  # This makes the projectile arc toward the target's height
  target_z = @player_z - 16  # Aim at player center (z + height/2, roughly)
  travel_tics = dist / speed
  travel_tics = 1.0 if travel_tics < 1.0
  ndz = (target_z - monster_z) / travel_tics

  @projectiles << Projectile.new(
    monster_x + ndx * 2, monster_y + ndy * 2, monster_z,
    ndx, ndy, ndz, proj_type, @tic, info[:sprite], :player
  )
end

#updateObject

Called each game tic



195
196
197
198
199
200
# File 'lib/doom/game/combat.rb', line 195

def update
  @tic += 1
  update_projectiles
  update_explosions
  @puffs.reject! { |p| @tic - p[:tic] > 12 }
end

#update_player_pos(x, y, z = nil) ⇒ Object



117
118
119
120
121
# File 'lib/doom/game/combat.rb', line 117

def update_player_pos(x, y, z = nil)
  @player_x = x
  @player_y = y
  @player_z = z if z
end