FlatWhite/Wall_Spawner.gd

267 lines
8.0 KiB
GDScript

extends Node2D
signal wall_spawned(gap_pos,gap_size,shift_pos)
signal laser_spawned(pos, n_lasers)
signal bomb_spawned(pos, n_spores)
signal query_player(callback)
signal wall_queue_empty
# Wall spawning properties
var topgap_max_offset = deg2rad(20)
var bottomgap_max_offset = deg2rad(40)
var wall_min_offset = deg2rad(5)
var wall_spawntime = 1
var last_wall_speed = 0
var wall_close_range = Global.WALL_WIDTH * 1.25
export var wall_density = 1.0
export var wall_dens_variance = 0.2
export var shift_probability = 0.5
# Wall density determines the timer for spawning the walls
# Laser spawning properties
const gap_proximity = 1
export var laser_density = 1.0
export var laser_dens_variance = 0.2
var laser_spawntime = 1
export var ordered_lasers = true
# Laser density determines the timer for spawning a laser
# Or determines the number of lasers spawned at a time for ordered_lasers
var spawning_walls = false
var spawning_lasers = false
var wall_queue = Array()
func _ready():
# Called when the node is added to the scene for the first time.
# Initialization here
calc_wall_spawntime()
$spawntimer.connect("timeout",self, "spawn_wall")
$spawntimer.start()
$lasertimer.set_wait_time(1)
$lasertimer.connect("timeout",self, "prepare_spawn_laser")
$lasertimer.start()
$bombtimer.set_wait_time(1)
$bombtimer.connect("timeout",self, "spawn_bomb")
$bombtimer.start()
func set_wall_spawntimer(timer=1):
$spawntimer.set_wait_time(timer)
wall_spawntime = timer
func set_laser_spawntimer(timer=1):
$lasertimer.set_wait_time(timer)
laser_spawntime = timer
func set_bomb_spawntimer(timer=1):
$bombtimer.set_wait_time(timer)
func set_ordered_laser(state):
ordered_lasers = state
func toggle_spawner(wall,laser,bomb):
if wall:
$spawntimer.start()
else:
$spawntimer.stop()
if laser:
$lasertimer.start()
else:
$lasertimer.stop()
if bomb:
$bombtimer.start()
else:
$bombtimer.stop()
spawning_walls = wall
spawning_lasers = laser
func activate_spawner():
$spawntimer.start()
func set_wall_density(mean, variance):
wall_density = mean
wall_dens_variance = variance
calc_wall_spawntime()
func set_shift_prob(val):
shift_probability = val
func set_laser_density(mean, variance):
laser_density = mean
laser_dens_variance = variance
calc_laser_spawntime()
func calc_wall_spawntime():
var current_speed = math.calc_wall_speed(wall_density)
set_wall_spawntimer(max(Global.WALL_WIDTH/current_speed/60 * 0.75,
math.negexp(2, wall_density*0.8 - rand_range(-wall_dens_variance,wall_dens_variance)*0.5)))
func calc_laser_spawntime():
if ordered_lasers:
set_laser_spawntimer(Global.LASER_FRAMES * Global.LASER_TIMER)
else:
set_laser_spawntimer(math.negexp(1, laser_density*0.6+rand_range(-laser_dens_variance,laser_dens_variance)*0.3))
func spawn_wall():
"""Procedure to spawn the walls is here"""
var min_shift
var max_shift
var top_max_shift
var bottom_max_shift
var gap_size
var wall_speed = math.calc_wall_speed(wall_density)
var dist_elapsed = wall_spawntime * last_wall_speed * 60
# Set the limits of the shift and define gap size
if dist_elapsed > wall_close_range:
dist_elapsed /= Global.GRID_SIZE
if $lasertimer.is_stopped():
min_shift = 2
top_max_shift = dist_elapsed * tan(topgap_max_offset) / max(1,wall_density)
bottom_max_shift = dist_elapsed * tan(bottomgap_max_offset) / max(1,wall_density)
gap_size = rand_range(1,max(5,15-Global.wall_difficulty))
else:
min_shift = 1
top_max_shift = dist_elapsed * tan(topgap_max_offset*0.8) / max(1,wall_density)
bottom_max_shift = top_max_shift
gap_size = rand_range(2,max(2,max(5,15-Global.wall_difficulty)))
else:
min_shift = 0
gap_size = rand_range(1,max(5,15-Global.wall_difficulty))
if wall_queue:
top_max_shift = max(0,abs(wall_queue.back().gap_size + gap_size)/2-1)
bottom_max_shift = top_max_shift
else:
top_max_shift = Global.HIGHEST_GRID
bottom_max_shift = top_max_shift
# Generate the gap position
var final_gap_pos
if not wall_queue:
final_gap_pos = Global.HIGHEST_GRID/2 + rand_range(-1,1) * Global.HIGHEST_GRID/4
else:
var last_pos = wall_queue.back().true_pos
if randf() > last_pos/Global.HIGHEST_GRID:
final_gap_pos = last_pos + rand_range(min_shift,top_max_shift)
else:
final_gap_pos = last_pos - rand_range(min_shift,bottom_max_shift)
final_gap_pos = max(min(final_gap_pos,Global.HIGHEST_GRID),Global.LOWEST_GRID)
# Generate the fake position
var fake_pos = final_gap_pos
if gap_size >1:
if math.rng_check(shift_probability):
fake_pos = rand_range(0, 30)
last_wall_speed = wall_speed
emit_signal("wall_spawned",Vector2(position.x, fake_pos),gap_size,final_gap_pos)
calc_wall_spawntime()
func prepare_spawn_laser():
emit_signal("query_player", funcref(self, "spawn_laser"))
func spawn_laser(player):
var n_lasers;
if $spawntimer.is_stopped():
if ordered_lasers:
n_lasers = math.inv_negexp(10, laser_density * 0.6 + (randf()*2 - 1) * laser_dens_variance)
else:
if Global.laser_difficulty > 5:
n_lasers = math.randint(1,2)
else:
n_lasers = math.randint(1,3)
for i in range(n_lasers):
var grid_pos = rand_range(Global.LOWEST_GRID,Global.HIGHEST_GRID)
emit_signal("laser_spawned",grid_pos,n_lasers)
else:
# Spawn only 3 lasers when walls are present
# And it is always ordered
n_lasers = math.randint(1,2)
# Given the laser timing t
if wall_queue:
var laser_time = Global.LASER_FRAMES * Global.LASER_TIMER
# First predict the player position after t time
# Assume player is perfect and crosses as many wall as possible
var frames = laser_time * 60; # Assume 60fps
var rpn = pow(1-player.POS_RECOVERY,frames)
var predicted_player_pos = ceil(player.position.x * rpn +\
player.default_pos * (1-rpn))
predicted_player_pos = max(predicted_player_pos, player.position.x)
# Using the predicted player position
# Find out which walls are of concern
var closest_wall_ind = search_woc(player.position.x)
var max_wall_ind = search_woc(predicted_player_pos, frames)
# Assuming all wall is of the same speed
# The wall relative distance to each other is constant
# Therefore, a binary search is possible on the queue
# Where-ever the search terminates, take the wall before it
var overlaps = Array()
for i in range(max_wall_ind-closest_wall_ind+1):
overlaps.append(0)
for i in range(n_lasers):
var grid_pos = rand_range(Global.LOWEST_GRID,Global.HIGHEST_GRID)
var discard = false
var ind = 0
for j in range(closest_wall_ind, max_wall_ind+1):
# When spawning a laser, check if the position is on the gap
var top = wall_queue[j].true_pos - wall_queue[j].gap_size/2
var bottom = wall_queue[j].true_pos + wall_queue[j].gap_size/2
# Keep track of how many lasers has spawned on the gap
if(grid_pos > top - gap_proximity&& grid_pos < bottom+gap_proximity):
# Number of laser on gap < gap_size//2
if overlaps[ind] > wall_queue[j].gap_size/2-1:
# Reroll position if above condition is violated
discard = true
break
overlaps[ind] += 1
ind += 1
if not discard:
emit_signal("laser_spawned",grid_pos,n_lasers)
calc_laser_spawntime()
func search_woc(pos, elapsed_time=0):
# Binary search the wall of concern, return index
# Need to account for the moving walls using elapsed time, 0 for static check
var lo = 0
var hi = wall_queue.size() - 1
while(lo <= hi):
var mid = (lo+hi)/2
var new_position = wall_queue[mid].position.x + wall_queue[mid].hspeed.x * elapsed_time
if (pos == new_position):
return mid
if(pos < new_position):
hi = mid - 1
if(pos > new_position):
lo = mid + 1
return math.bound_val(lo, 0, wall_queue.size() - 1)
func spawn_bomb():
var n_bombs = math.randint(1,3)
for i in range(n_bombs):
var grid_pos = math.randint(2,29)
var n_spores = math.randint(3,8)
emit_signal("bomb_spawned",grid_pos,n_spores)
func dequeue_wall(dummy):
wall_queue.pop_front()
if wall_queue.size() == 1:
emit_signal("wall_queue_empty")
func queue_wall(wall_ent):
wall_queue.append(wall_ent)