Class: Doom::Game::Combat
- Inherits:
-
Object
- Object
- Doom::Game::Combat
- Defined in:
- lib/doom/game/combat.rb
Overview
Combat resolution for every weapon and every damageable thing: hitscan (pistol/shotgun/chaingun), melee (fist/chainsaw), rockets and their splash, monster fireballs, barrel chain reactions, player-vs-player damage and frag scoring. Hitscan/aim matches Chocolate Doom's P_LineAttack / P_AimLineAttack (p_map.c); splash matches P_RadiusAttack (p_inter.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). Its HP comes from MONSTER_HP; its explosion reuses the shared SPLASH_* values.
2035- 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. :radius is the projectile's own collision radius, added to the player radius when testing a hit.
{ imp: { sprite: 'BAL1', speed: 10.0, damage: [3, 24], radius: 6 }, baron: { sprite: 'BAL7', speed: 15.0, damage: [8, 64], radius: 6 }, caco: { sprite: 'BAL2', speed: 10.0, damage: [5, 40], radius: 6 } }.freeze
- MONSTER_PROJECTILE_TYPE =
Map monster type to projectile type
{ 3001 => :imp, # Imp 3003 => :baron, # Baron 69 => :baron, # Hell Knight 3005 => :caco # Cacodemon }.freeze
- PLAYER_RADIUS =
Radius used when a shot or a rocket is tested against a player. Matches the 16 the monster-projectile collision already assumed.
16
Instance Attribute Summary collapse
-
#dead_things ⇒ Object
readonly
Returns the value of attribute dead_things.
-
#explosions ⇒ Object
readonly
Returns the value of attribute explosions.
-
#players ⇒ Object
Every player the world knows about.
-
#projectiles ⇒ Object
readonly
Returns the value of attribute projectiles.
-
#puffs ⇒ Object
readonly
Returns the value of attribute puffs.
-
#sprites ⇒ Object
readonly
Returns the value of attribute sprites.
Instance Method Summary collapse
- #dead?(thing_idx) ⇒ Boolean
-
#death_sprite(thing_idx, thing_type, viewer_angle, thing_angle) ⇒ Object
Get the current death frame sprite for a dead monster/barrel.
-
#fire(px, py, pz, cos_a, sin_a, weapon, shooter = nil) ⇒ Object
Fire the current weapon.
- #in_pain?(thing_idx) ⇒ Boolean
-
#initialize(map, sprites = nil, hidden_things = {}, sound_engine = nil, random: Random.new) ⇒ Combat
constructor
Weapon damage: DOOM does (P_Random()%3 + 1) * multiplier Pistol/chaingun: 15..35 = 5-15 per bullet Shotgun: 7 pellets, each 15..35 = 5-15 Fist/chainsaw: 12..32 = 2-10.
-
#spawn_monster_projectile(monster_x, monster_y, monster_z, monster_type, damage_multiplier, target) ⇒ Object
Spawn a monster projectile (fireball, etc.) Matches Chocolate Doom's P_SpawnMissile: calculates momz for vertical aim.
-
#update ⇒ Object
Called each game tic.
Constructor Details
#initialize(map, sprites = nil, hidden_things = {}, sound_engine = nil, random: Random.new) ⇒ Combat
Weapon damage: DOOM does (P_Random()%3 + 1) * multiplier Pistol/chaingun: 15..35 = 5-15 per bullet Shotgun: 7 pellets, each 15..35 = 5-15 Fist/chainsaw: 12..32 = 2-10
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/doom/game/combat.rb', line 111 def initialize(map, sprites = nil, hidden_things = {}, sound_engine = nil, random: Random.new) @map = map @random = random @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 @players = [] @tic = 0 end |
Instance Attribute Details
#dead_things ⇒ Object (readonly)
Returns the value of attribute dead_things.
164 165 166 |
# File 'lib/doom/game/combat.rb', line 164 def dead_things @dead_things end |
#explosions ⇒ Object (readonly)
Returns the value of attribute explosions.
164 165 166 |
# File 'lib/doom/game/combat.rb', line 164 def explosions @explosions end |
#players ⇒ Object
Every player the world knows about. Projectile collision, splash damage and monster aim all consider all of them, not one bound player.
130 131 132 |
# File 'lib/doom/game/combat.rb', line 130 def players @players end |
#projectiles ⇒ Object (readonly)
Returns the value of attribute projectiles.
164 165 166 |
# File 'lib/doom/game/combat.rb', line 164 def projectiles @projectiles end |
#puffs ⇒ Object (readonly)
Returns the value of attribute puffs.
164 165 166 |
# File 'lib/doom/game/combat.rb', line 164 def puffs @puffs end |
#sprites ⇒ Object (readonly)
Returns the value of attribute sprites.
164 165 166 |
# File 'lib/doom/game/combat.rb', line 164 def sprites @sprites end |
Instance Method Details
#dead?(thing_idx) ⇒ Boolean
170 171 172 |
# File 'lib/doom/game/combat.rb', line 170 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
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 |
# File 'lib/doom/game/combat.rb', line 175 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) return nil if thing_type == BARREL_TYPE && frame_idx >= frames.size 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(thing_type, frame_letter, viewer_angle, thing_angle) else @sprites.get_frame_by_prefix(prefix, frame_letter) end end |
#fire(px, py, pz, cos_a, sin_a, weapon, shooter = nil) ⇒ Object
Fire the current weapon. shooter is the Player pulling the trigger,
needed to credit a deathmatch kill and to stop a shot from hitting the
person who fired it.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/doom/game/combat.rb', line 212 def fire(px, py, pz, cos_a, sin_a, weapon, shooter = nil) case weapon when PlayerState::WEAPON_PISTOL, PlayerState::WEAPON_CHAINGUN hitscan(px, py, pz, cos_a, sin_a, 1, 0.0, 5, shooter) when PlayerState::WEAPON_SHOTGUN hitscan(px, py, pz, cos_a, sin_a, 7, Math::PI / 32, 5, shooter) when PlayerState::WEAPON_ROCKET spawn_rocket(px, py, pz, cos_a, sin_a, shooter) when PlayerState::WEAPON_FIST melee(px, py, cos_a, sin_a, 2, 64, shooter) when PlayerState::WEAPON_CHAINSAW melee(px, py, cos_a, sin_a, 2, 64, shooter) end end |
#in_pain?(thing_idx) ⇒ Boolean
166 167 168 |
# File 'lib/doom/game/combat.rb', line 166 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, target) ⇒ Object
Spawn a monster projectile (fireball, etc.) Matches Chocolate Doom's P_SpawnMissile: calculates momz for vertical aim
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 |
# File 'lib/doom/game/combat.rb', line 134 def spawn_monster_projectile(monster_x, monster_y, monster_z, monster_type, damage_multiplier, target) proj_type = MONSTER_PROJECTILE_TYPE[monster_type] return unless proj_type info = MONSTER_PROJECTILES[proj_type] return unless info dx = target.x - monster_x dy = target.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 = target.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, nil, damage_multiplier ) end |
#update ⇒ Object
Called each game tic
202 203 204 205 206 207 |
# File 'lib/doom/game/combat.rb', line 202 def update @tic += 1 update_projectiles update_explosions @puffs.reject! { |p| @tic - p[:tic] > 12 } end |