feat: bullrush branch - mekton bulls arena, HUD, NPC managers, godot_ai updates

This commit is contained in:
2026-07-01 10:39:21 +08:00
parent cc584c3251
commit b6b37b5aac
48 changed files with 2548 additions and 397 deletions
+109 -7
View File
@@ -134,6 +134,56 @@ func _normalize_tile(tile: int) -> int:
return tile - 4 # 11->7, 12->8, etc.
return tile
# =============================================================================
# Mekton Bulls mode helpers
# =============================================================================
func is_mekton_bulls_mode() -> bool:
return LobbyManager and LobbyManager.is_game_mode(GameMode.Mode.MEKTON_BULLS)
func _get_mekton_bulls_manager() -> Node:
if gauntlet_manager_override and is_instance_valid(gauntlet_manager_override):
return gauntlet_manager_override
var current = actor
while current != null:
var bm = current.get_node_or_null("MektonBullsManager")
if bm: return bm
current = current.get_parent()
var root = actor.get_tree().root
var main = root.get_node_or_null("Main")
if main:
return main.get_node_or_null("MektonBullsManager")
return null
func _get_active_bulls() -> Array:
return actor.get_tree().get_nodes_in_group("MektonBulls")
func _is_cell_unsafe_in_mekton_bulls(pos: Vector2i) -> bool:
"""Cell is unsafe if it's WATER, or if it's on the boundary (soon to be flooded)."""
if not is_mekton_bulls_mode(): return false
var bm = _get_mekton_bulls_manager()
if not bm: return false
# Check if water
var tile = enhanced_gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y))
if tile == 24: # TILE_WATER
return true
if bm.has_method("_is_boundary") and bm._is_boundary(pos):
return true
# Bull proximity
var bulls = _get_active_bulls()
for b in bulls:
var b_pos = enhanced_gridmap.local_to_map(b.position)
# If cell is adjacent to the bull, it's unsafe.
if abs(b_pos.x - pos.x) <= 1 and abs(b_pos.z - pos.y) <= 1:
return true
return false
# =============================================================================
# Goal Analysis
# =============================================================================
@@ -345,14 +395,28 @@ func find_best_tile_to_grab() -> Dictionary:
func find_nearest_tile_of_type(tile_types: Array) -> Vector2i:
"""Find nearest tile matching any type in array using optimized spiral search."""
var current_pos = actor.current_position
if not enhanced_gridmap:
return Vector2i(-1, -1)
if is_mekton_bulls_mode():
# Return the nearest uncollected tile from our blueprint
var bm = _get_mekton_bulls_manager()
var pid = actor.get("peer_id") if "peer_id" in actor else actor.name.to_int()
if bm and pid != null and bm.player_blueprints.has(pid):
var bp = bm.player_blueprints[pid]
var best_tile = Vector2i(-1, -1)
var best_dist = INF
for c in bp.cells:
if enhanced_gridmap.get_cell_item(Vector3i(c.x, 0, c.y)) == bp.color:
var dist = actor.current_position.distance_to(c)
if dist < best_dist and _is_valid_move_target(c, true):
best_dist = dist
best_tile = c
if best_tile != Vector2i(-1, -1):
return best_tile
var current_pos = actor.current_position
# Optimization: Start check at simple radius
# If we find something in the spiral, it is guaranteed to be one of the nearest (by Chebyshev distance logic broadly, or just good enough)
var max_radius = 25 # Limit search range to prevent full map scans on huge maps
if OS.has_feature("mobile"):
max_radius = 15 # Stricter limit on mobile
@@ -438,10 +502,43 @@ func find_nearest_roaming_tekton() -> Node3D:
# Movement Strategy
# =============================================================================
func _should_use_freeze() -> bool:
if not is_mekton_bulls_mode(): return false
var bm = _get_mekton_bulls_manager()
if not bm: return false
var pid = actor.get("peer_id") if "peer_id" in actor else actor.name.to_int()
if not bm.player_powers.has(pid) or bm.player_powers[pid]["FREEZE"] <= 0: return false
var bulls = _get_active_bulls()
var bot_pos = enhanced_gridmap.local_to_map(actor.position)
for b in bulls:
var b_pos = enhanced_gridmap.local_to_map(b.position)
if abs(bot_pos.x - b_pos.x) <= 3 and abs(bot_pos.z - b_pos.y) <= 3:
return true
return false
func find_optimal_move_target() -> Vector2i:
"""Calculate the best position to move towards."""
"""Core decision logic. Evaluates sabotaging vs making progress."""
var main = actor.get_tree().get_root().get_node_or_null("Main")
var is_sng = LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO)
if is_mekton_bulls_mode():
# In Mekton Bulls, use powers if viable.
if _should_use_freeze():
var bm = _get_mekton_bulls_manager()
var pid = actor.get("peer_id") if "peer_id" in actor else actor.name.to_int()
bm.try_use_freeze.rpc_id(1) # Try emitting to server
# Knock another nearby player
var mb_pid = actor.get("peer_id") if "peer_id" in actor else actor.name.to_int()
var mb_bm = _get_mekton_bulls_manager()
if mb_bm and mb_bm.player_powers.has(mb_pid) and mb_bm.player_powers[mb_pid]["KNOCK"] > 0:
var opps = _get_opponents()
for op in opps:
var dist = actor.position.distance_to(op.position)
if dist < enhanced_gridmap.cell_size.x * 2.0:
mb_bm.try_use_knock.rpc_id(1, op.name.to_int(), actor.position.direction_to(op.position).normalized())
break
var is_sng = LobbyManager and LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO)
var gc_manager = main.get_node_or_null("GoalsCycleManager") if main else null
var time_left = gc_manager.get_global_time_remaining() if gc_manager else 999.0
var is_match_running = gc_manager.is_match_running() if gc_manager else false
@@ -602,6 +699,11 @@ func _is_valid_move_target(pos: Vector2i, ignore_players: bool = false) -> bool:
if not enhanced_gridmap or not enhanced_gridmap.is_position_valid(pos):
return false
if is_mekton_bulls_mode():
# Do not move into WATER or the boundary
if _is_cell_unsafe_in_mekton_bulls(pos):
return false
# Check Floor 0 (Ground/Walls)
var floor_item = enhanced_gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y))
if floor_item == -1 or floor_item in enhanced_gridmap.non_walkable_items: