update scarcity
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
extends Node
|
||||
class_name ModularMapGenerator
|
||||
|
||||
# ModularMapGenerator - Procedural generation for Floor 0 (Ground)
|
||||
|
||||
const GROUND_ITEM = 0
|
||||
const WALL_ITEM = 4 # Or Void if using empty cells
|
||||
|
||||
# Generation Parameters
|
||||
var grid_width: int = 14
|
||||
var grid_depth: int = 14
|
||||
var min_walkable_cells: int = 20 # Minimum for 12 players + space
|
||||
var max_walkable_cells: int = 100
|
||||
var connectivity_guaranteed: bool = true
|
||||
|
||||
# Shapes / Strategies
|
||||
enum MapShape {
|
||||
RECTANGLE,
|
||||
ISLANDS,
|
||||
MAZE_LIKE,
|
||||
ROOMS,
|
||||
RANDOM_NOISE
|
||||
}
|
||||
|
||||
func generate_map(gridmap: Node, width: int, depth: int, shape: int = MapShape.ROOMS) -> bool:
|
||||
grid_width = width
|
||||
grid_depth = depth
|
||||
|
||||
var grid_data = [] # 2D array [z][x]
|
||||
|
||||
# Initialize with walls/void
|
||||
for z in range(depth):
|
||||
var row = []
|
||||
for x in range(width):
|
||||
row.append(WALL_ITEM)
|
||||
grid_data.append(row)
|
||||
|
||||
# Apply generation strategy
|
||||
match shape:
|
||||
MapShape.RECTANGLE:
|
||||
_generate_rectangle(grid_data)
|
||||
MapShape.ISLANDS:
|
||||
_generate_islands(grid_data)
|
||||
MapShape.ROOMS:
|
||||
_generate_rooms(grid_data)
|
||||
MapShape.RANDOM_NOISE:
|
||||
_generate_noise(grid_data)
|
||||
_:
|
||||
_generate_rooms(grid_data) # Default
|
||||
|
||||
# Ensure connectivity
|
||||
if connectivity_guaranteed:
|
||||
_ensure_connectivity(grid_data)
|
||||
|
||||
# Ensure minimum walkable count (by growing if needed)
|
||||
_ensure_min_walkable(grid_data)
|
||||
|
||||
# Apply to GridMap
|
||||
_apply_to_gridmap(gridmap, grid_data)
|
||||
|
||||
return true
|
||||
|
||||
func _generate_rectangle(grid: Array):
|
||||
# Simple rectangle with some noise edges?
|
||||
for z in range(1, grid_depth - 1):
|
||||
for x in range(1, grid_width - 1):
|
||||
grid[z][x] = GROUND_ITEM
|
||||
|
||||
func _generate_noise(grid: Array):
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.randomize()
|
||||
for z in range(1, grid_depth - 1):
|
||||
for x in range(1, grid_width - 1):
|
||||
if rng.randf() > 0.4:
|
||||
grid[z][x] = GROUND_ITEM
|
||||
|
||||
func _generate_rooms(grid: Array):
|
||||
# Random walk rooms
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.randomize()
|
||||
|
||||
var room_count = rng.randi_range(3, 6)
|
||||
var rooms = [] # Rects
|
||||
|
||||
for i in range(room_count):
|
||||
var w = rng.randi_range(3, 6)
|
||||
var h = rng.randi_range(3, 6)
|
||||
var x = rng.randi_range(1, grid_width - w - 1)
|
||||
var z = rng.randi_range(1, grid_depth - h - 1)
|
||||
|
||||
# Carve room
|
||||
for rz in range(z, z + h):
|
||||
for rx in range(x, x + w):
|
||||
grid[rz][rx] = GROUND_ITEM
|
||||
|
||||
# Connect to previous room
|
||||
if i > 0:
|
||||
var prev = rooms[i-1]
|
||||
_connect_points(grid, Vector2i(prev[0], prev[1]), Vector2i(x, z))
|
||||
|
||||
rooms.append([x, z, w, h])
|
||||
|
||||
func _generate_islands(grid: Array):
|
||||
# Cellular automata smoothing
|
||||
_generate_noise(grid)
|
||||
|
||||
# Smoothing passes
|
||||
for i in range(3):
|
||||
var new_grid = grid.duplicate(true)
|
||||
for z in range(1, grid_depth - 1):
|
||||
for x in range(1, grid_width - 1):
|
||||
var neighbors = _count_neighbors(grid, x, z)
|
||||
if neighbors > 4:
|
||||
new_grid[z][x] = GROUND_ITEM
|
||||
elif neighbors < 3:
|
||||
new_grid[z][x] = WALL_ITEM
|
||||
grid = new_grid
|
||||
|
||||
func _count_neighbors(grid: Array, x: int, z: int) -> int:
|
||||
var count = 0
|
||||
for dz in range(-1, 2):
|
||||
for dx in range(-1, 2):
|
||||
if dz == 0 and dx == 0: continue
|
||||
if grid[z + dz][x + dx] == GROUND_ITEM:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
func _connect_points(grid: Array, p1: Vector2i, p2: Vector2i):
|
||||
var curr = p1
|
||||
while curr != p2:
|
||||
grid[curr.y][curr.x] = GROUND_ITEM
|
||||
if curr.x != p2.x:
|
||||
curr.x += 1 if p2.x > curr.x else -1
|
||||
elif curr.y != p2.y:
|
||||
curr.y += 1 if p2.y > curr.y else -1
|
||||
grid[p2.y][p2.x] = GROUND_ITEM
|
||||
|
||||
func _ensure_connectivity(grid: Array):
|
||||
# Flood fill from first walkable
|
||||
var start = Vector2i(-1, -1)
|
||||
var all_walkable = []
|
||||
|
||||
for z in range(grid_depth):
|
||||
for x in range(grid_width):
|
||||
if grid[z][x] == GROUND_ITEM:
|
||||
if start == Vector2i(-1, -1):
|
||||
start = Vector2i(x, z)
|
||||
all_walkable.append(Vector2i(x, z))
|
||||
|
||||
if start == Vector2i(-1, -1):
|
||||
return # Empty map
|
||||
|
||||
var visited = {}
|
||||
var queue = [start]
|
||||
visited[start] = true
|
||||
var connected_count = 0
|
||||
|
||||
while not queue.is_empty():
|
||||
var curr = queue.pop_front()
|
||||
connected_count += 1
|
||||
|
||||
# Check neighbors
|
||||
var dirs = [Vector2i(0, 1), Vector2i(0, -1), Vector2i(1, 0), Vector2i(-1, 0)]
|
||||
for d in dirs:
|
||||
var next = curr + d
|
||||
if next.x >= 0 and next.x < grid_width and next.y >= 0 and next.y < grid_depth:
|
||||
if grid[next.y][next.x] == GROUND_ITEM and not visited.has(next):
|
||||
visited[next] = true
|
||||
queue.append(next)
|
||||
|
||||
# If disconnected parts, keep largest island or removing others?
|
||||
# Better: grow bridges. But for simplicity, let's just use the main island
|
||||
# and turn others to walls? Or connect them?
|
||||
# For "Rooms" geneation, we explicitly connected them.
|
||||
# For "Islands", let's just keep the biggest island or assume automata made big blobs.
|
||||
|
||||
if connected_count < all_walkable.size():
|
||||
# Prune disconnected (simple approach)
|
||||
for pos in all_walkable:
|
||||
if not visited.has(pos):
|
||||
grid[pos.y][pos.x] = WALL_ITEM
|
||||
|
||||
func _ensure_min_walkable(grid: Array):
|
||||
var walkable = []
|
||||
for z in range(grid_depth):
|
||||
for x in range(grid_width):
|
||||
if grid[z][x] == GROUND_ITEM:
|
||||
walkable.append(Vector2i(x, z))
|
||||
|
||||
if walkable.size() < min_walkable_cells:
|
||||
# Force create a central room
|
||||
var center_x = grid_width / 2
|
||||
var center_z = grid_depth / 2
|
||||
var radius = int(sqrt(min_walkable_cells)) / 2 + 1
|
||||
for z in range(center_z - radius, center_z + radius):
|
||||
for x in range(center_x - radius, center_x + radius):
|
||||
if x >= 0 and x < grid_width and z >= 0 and z < grid_depth:
|
||||
grid[z][x] = GROUND_ITEM
|
||||
|
||||
func _apply_to_gridmap(gridmap: Node, grid: Array):
|
||||
for z in range(grid_depth):
|
||||
for x in range(grid_width):
|
||||
var item = grid[z][x]
|
||||
# Floor 0 logic
|
||||
gridmap.set_cell_item(Vector3i(x, 0, z), item)
|
||||
@@ -159,8 +159,46 @@ func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int):
|
||||
# 3f. Reset the UI for the player who acted
|
||||
player.rpc("force_action_state_none")
|
||||
|
||||
# 4. Check if we need to respawn tiles (Scarcity Logic)
|
||||
# "during no more tiles" -> Refill if floor is empty
|
||||
_check_and_refill_grid_if_needed(server_gridmap)
|
||||
|
||||
return true
|
||||
|
||||
func _check_and_refill_grid_if_needed(server_gridmap: Node):
|
||||
# Check if there are any items left on floor 1
|
||||
var has_items = false
|
||||
var item_list = server_gridmap.mesh_library.get_item_list() if server_gridmap.mesh_library else []
|
||||
|
||||
# We can't efficiently iterate all cells, but we can check usage via get_used_cells_by_item is too specific
|
||||
# Iterating columns/rows is safe for this map size (10x10 usually)
|
||||
for x in range(server_gridmap.columns):
|
||||
for z in range(server_gridmap.rows):
|
||||
var item = server_gridmap.get_cell_item(Vector3i(x, 1, z))
|
||||
if item != -1:
|
||||
has_items = true
|
||||
break
|
||||
if has_items:
|
||||
break
|
||||
|
||||
if not has_items:
|
||||
print("[PlayerboardManager] Floor 1 empty! Respawning tiles with Scarcity...")
|
||||
# Call randomize_floor on floor 1
|
||||
# Since Main owns gridmap, we can sync this or just do it server side (which we are)
|
||||
server_gridmap.randomize_floor(1)
|
||||
|
||||
# We need to sync the ENTIRE floor to clients.
|
||||
# EnhancedGridMap doesn't have a "Sync Floor" RPC built-in to Main, only single cells or array update.
|
||||
# Main.gd has sync_grid_item which handles single cells.
|
||||
# Ideally we'd loop and sync all, or add a method to Main to sync floor.
|
||||
# Loop is okay for 10x10 (100 RPCs is a bit much but acceptable for a rare event).
|
||||
var main = player.get_tree().get_root().get_node_or_null("Main")
|
||||
if main:
|
||||
for x in range(server_gridmap.columns):
|
||||
for z in range(server_gridmap.rows):
|
||||
var item = server_gridmap.get_cell_item(Vector3i(x, 1, z))
|
||||
main.rpc("sync_grid_item", x, 1, z, item)
|
||||
|
||||
func bot_try_grab_item() -> bool:
|
||||
if not enhanced_gridmap or player.action_points <= 0:
|
||||
return false
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
extends Node
|
||||
class_name ScarcityManager
|
||||
|
||||
# ScarcityManager - Handles weighted random, tile generation for gameplay balance
|
||||
|
||||
const TILES = [7, 8, 9, 10]
|
||||
const SPECIAL_TILES = [11, 12, 13, 14]
|
||||
|
||||
# Weights (higher = more common)
|
||||
# Standard tiles: Heart, Diamond, Star, Coin
|
||||
const TILE_WEIGHTS = {
|
||||
7: 100, # Heart
|
||||
8: 100, # Diamond
|
||||
9: 100, # Star
|
||||
10: 100 # Coin
|
||||
}
|
||||
|
||||
# Special tiles: Burn, Spawn, Freeze, Block, Invisible
|
||||
const SPECIAL_WEIGHTS = {
|
||||
11: 5,
|
||||
12: 5,
|
||||
13: 5,
|
||||
14: 5
|
||||
}
|
||||
|
||||
static func get_random_tile_id() -> int:
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.randomize()
|
||||
|
||||
var total_weight = 0
|
||||
var pool = {}
|
||||
|
||||
# Add standard tiles
|
||||
for id in TILE_WEIGHTS:
|
||||
pool[id] = TILE_WEIGHTS[id]
|
||||
total_weight += TILE_WEIGHTS[id]
|
||||
|
||||
# Add special tiles
|
||||
for id in SPECIAL_WEIGHTS:
|
||||
pool[id] = SPECIAL_WEIGHTS[id]
|
||||
total_weight += SPECIAL_WEIGHTS[id]
|
||||
|
||||
var roll = rng.randi_range(0, total_weight - 1)
|
||||
var current = 0
|
||||
|
||||
for id in pool:
|
||||
current += pool[id]
|
||||
if roll < current:
|
||||
return id
|
||||
|
||||
return 7 # Fallback to Heart
|
||||
Reference in New Issue
Block a user