Class: Quake::Game::BrushEntities

Inherits:
Object
  • Object
show all
Defined in:
lib/quake/game/brush_entities.rb

Overview

Manages brush entity behavior: doors, platforms, buttons, triggers. Each entity has a state machine and moves between positions.

Constant Summary collapse

BUTTON_TOUCH_RADIUS =
40.0
STATE_IDLE =

Entity states

:idle
STATE_OPENING =
:opening
STATE_OPEN =
:open
STATE_CLOSING =
:closing
STATE_CLOSED =
:closed
PLAT_LOW_TRIGGER =
1
FL_FLY =
1
FL_SWIM =
2
FL_MONSTER =
32
FL_ONGROUND =
512
VERTICAL_RIDER_CLASSNAMES =

If the player is standing on a moving platform, return a new position snapped to the platform's top surface (so on_ground stays true and the player rides smoothly). Returns nil if not on a moving platform.

%w[func_plat plat func_door door].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(entities, level, target_map, registered: false, sound_events: nil, serverflags: 0, worldtype: 0) ⇒ BrushEntities

Returns a new instance of BrushEntities.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/quake/game/brush_entities.rb', line 23

def initialize(entities, level, target_map, registered: false, sound_events: nil, serverflags: 0, worldtype: 0)
  @entities = entities
  @level = level
  @target_map = target_map
  @registered = registered
  @sound_events = sound_events
  @serverflags = serverflags.to_i
  @worldtype = worldtype.to_i
  @time = 0.0
  @total_secrets = 0
  @found_secrets = 0
  @brush_entities = entities.select { |ent| ent.brush_entity? || ent.classname == "misc_teleporttrain" }
  @delayed_uses = []

  @entities.each { |ent| init_point_trigger(ent) }
  # Initialize movement data for each brush entity
  @brush_entities.each { |ent| init_brush_entity(ent) }
  link_doors

  puts "Initialized #{@brush_entities.size} brush entities"
end

Instance Attribute Details

#found_secretsObject (readonly)

Returns the value of attribute found_secrets.



21
22
23
# File 'lib/quake/game/brush_entities.rb', line 21

def found_secrets
  @found_secrets
end

#total_secretsObject (readonly)

Returns the value of attribute total_secrets.



21
22
23
# File 'lib/quake/game/brush_entities.rb', line 21

def total_secrets
  @total_secrets
end

Instance Method Details

#check_triggers(player_pos, player_radius: 16.0, player_forward: nil) ⇒ Object

Check if player touches any trigger entities



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
# File 'lib/quake/game/brush_entities.rb', line 130

def check_triggers(player_pos, player_radius: 16.0, player_forward: nil)
  @entities.each do |ent|
    next unless ent.classname.start_with?("trigger_")
    next unless trigger_model_index(ent)
    next if ent.instance_variable_get(:@removed)
    next if ent.instance_variable_get(:@touch_disabled)
    next if notouch_trigger?(ent)
    next if health_trigger?(ent)
    next unless trigger_facing_allowed?(ent, player_forward)
    next if trigger_cooldown_active?(ent)

    model = @level.models[trigger_model_index(ent)]
    next unless model

    # Simple AABB check against player
    mins = model.mins
    maxs = model.maxs
    origin = ent.position

    if player_pos.x >= origin.x + mins.x - player_radius &&
       player_pos.x <= origin.x + maxs.x + player_radius &&
       player_pos.y >= origin.y + mins.y - player_radius &&
       player_pos.y <= origin.y + maxs.y + player_radius &&
       player_pos.z >= origin.z + mins.z - player_radius &&
       player_pos.z <= origin.z + maxs.z + player_radius
      yield ent if block_given?
    end
  end
end

#damage_brush_entity(ent, amount, activator: nil) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/quake/game/brush_entities.rb', line 220

def damage_brush_entity(ent, amount, activator: nil)
  return damage_button(ent, amount, activator: activator) if ent.classname == "func_button"
  return damage_door(ent, amount, activator: activator) if regular_door?(ent)
  return damage_secret_door(ent, activator: activator) if secret_door?(ent)
  return false unless ["trigger_once", "trigger_multiple", "trigger_secret"].include?(ent.classname)
  return false unless ent.health.positive?

  ent.instance_variable_set(:@max_health, ent.health) unless ent.instance_variable_get(:@max_health)
  ent.health -= amount.to_f.ceil
  return true if ent.health.positive?

  ent.health = -99.0 if ent.health < -99.0
  activate_multi_trigger(ent, activator: activator || ent)
  true
end

#damage_trigger(ent, amount, activator: nil) ⇒ Object



216
217
218
# File 'lib/quake/game/brush_entities.rb', line 216

def damage_trigger(ent, amount, activator: nil)
  damage_brush_entity(ent, amount, activator: activator)
end

#platform_motion_for(player_pos) ⇒ Object

Backward-compatible delta-only method



123
124
125
126
127
# File 'lib/quake/game/brush_entities.rb', line 123

def platform_motion_for(player_pos)
  snapped = snap_to_platform(player_pos)
  return Math::Vec3::ORIGIN unless snapped
  snapped - player_pos
end

#snap_to_platform(player_pos) ⇒ Object



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
# File 'lib/quake/game/brush_entities.rb', line 89

def snap_to_platform(player_pos)
  @brush_entities.each do |ent|
    next unless VERTICAL_RIDER_CLASSNAMES.include?(ent.classname)
    model = @level.models[ent.model_index]
    next unless model

    prev = ent.instance_variable_get(:@prev_pos)
    next unless prev

    ent_delta_z = ent.position.z - prev.z
    # Doors join the rider check only while actually moving
    # vertically (elevators like e1m1's t1 are vertical func_doors --
    # SV_PushMove carries riders for every pusher, not just plats).
    next if ent.classname.include?("door") && ent_delta_z.zero?

    # Use the OLD platform position for the on_top check, since
    # player_pos hasn't been updated for this frame's movement yet.
    # The player origin rides 24 above the surface (hull mins.z -24).
    old_top_z = prev.z + model.maxs.z
    next unless player_pos.z >= old_top_z + 16 && player_pos.z <= old_top_z + 48
    next unless player_pos.x >= ent.position.x + model.mins.x &&
                player_pos.x <= ent.position.x + model.maxs.x &&
                player_pos.y >= ent.position.y + model.mins.y &&
                player_pos.y <= ent.position.y + model.maxs.y

    # Snap the origin to 24 above the new top surface (feet slightly
    # above it so the trace-down ground check detects it cleanly).
    new_top_z = ent.position.z + model.maxs.z
    return Math::Vec3.new(player_pos.x, player_pos.y, new_top_z + 24.5)
  end
  nil
end

#touch_trigger(ent, activator: nil) ⇒ Object



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
# File 'lib/quake/game/brush_entities.rb', line 184

def touch_trigger(ent, activator: nil)
  return false if ent.instance_variable_get(:@touch_disabled)
  return false if trigger_cooldown_active?(ent)

  case ent.classname
  when "trigger_once"
    return false unless activator&.classname == "player"
    return false unless trigger_touch_facing_allowed?(ent, activator)

    activate_multi_trigger(ent, activator: activator)
  when "trigger_secret"
    return false unless trigger_touch_facing_allowed?(ent, activator)

    touch_secret_trigger(ent, activator: activator)
  when "trigger_multiple"
    return false unless activator&.classname == "player"
    return false unless trigger_touch_facing_allowed?(ent, activator)

    activate_multi_trigger(ent, activator: activator)
  when "trigger_onlyregistered"
    touch_onlyregistered(ent, activator: activator)
  when "trigger_monsterjump"
    touch_monsterjump(ent, activator)
  when "trigger_push"
    touch_push(ent, activator)
  when "trigger_hurt"
    touch_hurt(ent, activator)
  else
    false
  end
end

#update(dt, player_pos, player_state: nil) ⇒ Object

Update all brush entities. Returns entities that need rendering.



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
# File 'lib/quake/game/brush_entities.rb', line 46

def update(dt, player_pos, player_state: nil)
  @time_step = dt
  @time += dt
  update_delayed_uses(dt)
  @entities.each { |ent| update_scheduled_remove(ent, dt) }

  # Save previous positions for platform-riding
  @brush_entities.each do |ent|
    ent.instance_variable_set(:@prev_pos, ent.position)
  end

  @brush_entities.each do |ent|
    next if ent.instance_variable_get(:@removed)

    update_trigger_cooldown(ent, dt)

    case ent.classname
    when "func_door", "door"
      if secret_door?(ent)
        update_secret_door(ent, player_pos, player_state)
      else
        update_door(ent, dt, player_pos, player_state)
      end
    when "func_plat", "plat"
      update_platform(ent, dt, player_pos, player_state)
    when "func_button"
      update_button(ent, dt, player_pos, player_state)
    when "func_train", "train", "misc_teleporttrain"
      update_train(ent, dt, player_pos, player_state)
    when "func_door_secret"
      update_secret_door(ent, player_pos, player_state)
    end
  end

  @brush_entities
end

#use_targets(source, activator: nil) ⇒ Object

QuakeC SUB_UseTargets, limited to local entity state transitions. The engine owns side effects like changelevel and teleport movement.



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/quake/game/brush_entities.rb', line 162

def use_targets(source, activator: nil)
  delay = (source["delay"] || "0").to_f
  unless delay.zero?
    @delayed_uses << {
      remaining: delay,
      message: source.message,
      killtarget: source.killtarget,
      target: source.target,
      activator: activator || source
    }
    return
  end

  fire_use_targets(
    source.killtarget,
    source.target,
    activator: activator || source,
    message: source.message,
    source_noise: source.instance_variable_get(:@noise)
  )
end