From d22224a818cf627eb755bb598825e8262778d69b Mon Sep 17 00:00:00 2001 From: Kienan Stewart Date: Sun, 3 Apr 2022 10:24:33 -0400 Subject: [PATCH] Add enemy respawns --- TODO.md | 14 +++--- assets/export/edge.png | Bin 892 -> 891 bytes src/Game.gd | 97 +++++++++++++++++++++++++++++++++++++++-- src/Piece.gd | 6 +-- src/Playfield.gd | 1 + src/Square.gd | 32 +++++++++++++- src/Square.tscn | 10 +++++ 7 files changed, 143 insertions(+), 17 deletions(-) diff --git a/TODO.md b/TODO.md index 6322252..4d0f357 100644 --- a/TODO.md +++ b/TODO.md @@ -1,22 +1,18 @@ -1. Enemy respawns - * every now and again, the opponents has new units that spawn - * indicator to show respawn is nearing - * is it all the missing units, or a chance for each? -2. Power ups +1. Power ups * a chance of having power ups spawn on an random unoccupied square when a unit dies * +health, +attack, +speed, +jump, (pawn only) remove "attack_only", spawn a new (random?) piece, change movement type * then the enemy respawns units, their new units are stronger * support for pieces with multiple hit points * unit info panel -3. Visual polish +2. Visual polish * clean up tile borders * multiple square tiles to add variation * make the help text indicate (flash, etc.) -4. Sound effects +3. Sound effects * on hit * on piece lost * on piece kill * on opponent victory * if possible, a small bit of background music -5. Further visual polish -6. New units +4. Further visual polish +5. New units diff --git a/assets/export/edge.png b/assets/export/edge.png index b5ababd7832a28cd2faf525d1b5714162cb6aafb..23fbaf91e547d8b3a2eb513213130ce18b6295de 100644 GIT binary patch delta 424 zcmV;Z0ayO~2Kxq(B!7cxLqkwWLqi~Na&Km7Y-IodD3N`UJxIeq9K~PLiXv49JBT=B zs7@9{MIE&YMW_&Jg;pI*F8zWg4M~cNqu^R_@ME#+;Nq;SgR3A2etXocD<%tSGC*=ftBXU6A;Z>x##3oC_`sJTqdZ zQ}e_TVzJQ1N*lAHsS!^SM^#Oyd?Dkp%6W^kR<5$okXuz#^6)L4<-T zN+`odj8>f#3x6rvkNfxsUB5&wg#t9I72Cnp$zfuQgK1r{&wCEAgw+&oew=`uBxZD8-o($QPT`5RY$mfCg zGy0}1(0>bbuX(*S_i_3Fq^Yaq4RCM>j1(w)-RIq1?GL^Ed#2gn4=l5Ci<}2i0h6%- SG6(|>0T}BQ-wd-!m6rnnNgNw7S4z7YA_yOYRga5(rZq35vgqswK16?n+{V@y# zcY$Wzw!e>UyLkfmpMfi_?XNa~nNQN|Z7q5P^lt+f*KJMS11@)f!6#iZBuDbo6bc34 z{fxdT2MpW-J!@`nt$mz602%5kc>^3A0wYDrUiWx+cMxZ9|DI{}_XABH(1fHZa3R diff --git a/src/Game.gd b/src/Game.gd index 3a8c620..574e96d 100644 --- a/src/Game.gd +++ b/src/Game.gd @@ -32,6 +32,9 @@ var flash_help = null var rng = null var ai_target = null var ai_piece = null +var reinforcements = null +var reinforcements_size = 0 +var reinforcements_coords = [] const piece_types = { "pawn": "res://src/pieces/Pawn.tscn", @@ -165,7 +168,7 @@ func _on_piece_click(piece, event): if piece == null: self.selected_piece = null return - print("Selected piece: ", piece) + #print("Selected piece: ", piece) if piece.is_in_group("player"): piece.get_node("Body").set_modulate(Color(0, 1, 0, 1)) else: @@ -278,7 +281,8 @@ func reset_game_state(): self.board_squares[Vector2(x, y)] = { "x": x, "y": y, - "piece": null + "piece": null, + "reinforcement": null, } y += 1 x += 1 @@ -321,13 +325,97 @@ func _on_phase_end(): self.current_state += 1; if not self.current_state in self.states.keys(): self.current_state = 0 - self.turn += 1 self._on_new_turn() get_node("TopBar/Bottom/Instruction").set_text( self.states[self.current_state]["description"] + " - " + self.states[self.current_state]["directive"] ) +func choose_reinforcements(size, opponent: bool = true, at_spawn = true): + var min_square = 0 + var max_square = (self.height * self.width) - 1 + if at_spawn: + max_square = (self.width)*2 - 1 + var range_size = max_square - min_square + var arrival_coords = [] + var n = 0 + while n < size: + var try = 0 + var square_index = 0 + var square = null + while try < 10: + # Try to get an unoccupied square + square_index = self.rng.randi() % range_size + var square_coord = Vector2( + floor(square_index % self.width), floor(square_index / self.width) + ) + if at_spawn and not opponent: + square_coord.y += self.height - 2 + square = self.board_squares[square_coord] + if square['piece'] == null and square['reinforcement'] == null: + # unoccupied, we'll take it + arrival_coords.append(square_coord) + var types = self.piece_types.keys() + square['reinforcement'] = types[self.rng.randi()%types.size()] + break + # occupied, try again + square = null + try += 1 + if square == null: + print("Failed to find a spot to spawn reinforcement") + n += 1 + return arrival_coords + func _on_new_turn(): + self.turn += 1 + var just_spawned = false + if self.reinforcements != null: + var pf = get_node("/root/Game/MarginContainer/Playfield") + self.reinforcements -= 1 + if self.reinforcements == 1: + # decide on what will arrive + self.reinforcements_coords = choose_reinforcements(self.reinforcements_size) + # show the player where they will arrive + for coord in self.reinforcements_coords: + pf.squares[coord].get_ref().set_reinforcement(self.board_squares[coord]['reinforcement']) + print("Reinforcement at coord ", coord, ": ", self.board_squares[coord]['reinforcement']) + get_node("BottomBar/Help").set_text("Reinforcement arrival locations analyzed") + self.flash_help = 3 + elif self.reinforcements == 0: + # spawn them + for coord in self.reinforcements_coords: + pf.squares[coord].get_ref().set_reinforcement(null) + var square = self.board_squares[coord] + if square['piece'] == null: + new_piece(square['reinforcement'], "opponent", coord) + else: + print("Reinforcement arrival at ", coord, " blocked by piece!") + get_node("BottomBar/Help").set_text("Reinforcement at " + str(coord) + " telefragged.") + self.flash_help = 2 + square['reinforcement'] = null + just_spawned = true + self.reinforcements = null + self.reinforcements_size = 0 + self.reinforcements_coords = [] + + # Check for reinforcements + var opponent_pieces = get_tree().get_nodes_in_group("opponent") + # @TODO hardcoded value for number of pieces a side should have on the board + # If they are less than 1/4 strength ensure that reinforcements are queued + if opponent_pieces.size() <= 3 and self.reinforcements == null: + self.reinforcements = 2 # 2 turns away + self.reinforcements_size = 4 + get_node("BottomBar/Help").set_text("Multiple opponent reinforcements detected inbound") + self.flash_help = 3 + if self.reinforcements == null and not just_spawned: + var chance = lerp(0, 50, 1 - float(opponent_pieces.size())/16.0) + var i = self.rng.randi() % 100 + print("Roll for reinforcements ", i, " < ", chance) + if i < chance: + self.reinforcements = 2 + self.reinforcements_size = 1 + get_node("BottomBar/Help").set_text("Inbound opponent reinforcements detected") + self.flash_help = 3 + get_node("TopBar/Top/HBoxContainer/Turn").set_text(str(self.turn)) func _reset_help(): @@ -341,7 +429,8 @@ func _process(delta): else: self._reset_help() self.flash_help = null - if get_tree().get_nodes_in_group("opponent").empty() or get_tree().get_nodes_in_group("player").empty(): + var opponent_pieces = get_tree().get_nodes_in_group("opponent") + if opponent_pieces.empty() or get_tree().get_nodes_in_group("player").empty(): # The game is over self.current_state = 99 var player_victory = false diff --git a/src/Piece.gd b/src/Piece.gd index a869e3b..b5bdff7 100644 --- a/src/Piece.gd +++ b/src/Piece.gd @@ -40,7 +40,7 @@ func _input(event): get_tree().set_input_as_handled() else: if self.last_click != null and self.last_click <= CLICK_THRESHOLD: - print("Click: ", self, event) + #print("Click: ", self, event) emit_signal("click", self, event) # Work-around bug where only the last signal is connected # to the Game @@ -52,7 +52,7 @@ func _input(event): get_tree().set_input_as_handled() if not event.pressed and self.hold_started: emit_signal("hold_stop", self, event) - print("Hold stop", self, " ", event) + #print("Hold stop", self, " ", event) self.cancel_hold() get_tree().set_input_as_handled() @@ -62,7 +62,7 @@ func _process(delta): self.last_click += delta if not self.hold_started and self.last_click >= self.CLICK_THRESHOLD: self.hold_started = true; - print("Hold start", self, " ", null) + #print("Hold start", self, " ", null) emit_signal("hold_start", self, null) if self.hold_started: self.set_global_position(get_viewport().get_mouse_position()) diff --git a/src/Playfield.gd b/src/Playfield.gd index 527af5f..fbfc98e 100644 --- a/src/Playfield.gd +++ b/src/Playfield.gd @@ -29,6 +29,7 @@ func initialize(width: int = 8, height: int = 8): # @TODO any tweaks to the node by calling custom function initialize() instance.translate(Vector2(128*i, 128*j)) self.squares[Vector2(i, j)] = weakref(instance) + instance.get_node("Control").set_tooltip(str(Vector2(i, j))) add_child(instance) j += 1 i += 1 diff --git a/src/Square.gd b/src/Square.gd index 0d8842a..910127e 100644 --- a/src/Square.gd +++ b/src/Square.gd @@ -5,11 +5,41 @@ class_name Square # var a = 2 # var b = "text" - +var reinforcement = null +var timer = 0 +const FLASH_FREQUENCY = 2 # seconds +const FLASH_MIN = Color(1, 0.75, 0.75, 1) +const FLASH_MAX = Color(1, 0.5, 0.5, 1) +const DEFAULT_BODY = Color(1, 1, 1, 1) # Called when the node enters the scene tree for the first time. func _ready(): pass # Replace with function body. +func set_reinforcement(reinforcement): + self.reinforcement = reinforcement + self.timer = 0 + print("Set reinforcement on ", self, " : ", reinforcement) + if reinforcement == null: + get_node("Body").set_modulate(DEFAULT_BODY) + get_node("Control").set_tooltip("") + else: + get_node("Control").set_tooltip("Arriving unit: " + reinforcement) + +func _process(delta): + if self.reinforcement != null: + self.timer += delta + if self.timer >= FLASH_FREQUENCY*2: + self.timer = 0 + if self.timer >= FLASH_FREQUENCY: + # Going from dark to light + get_node("Body").set_modulate( + lerp(FLASH_MIN, FLASH_MAX, 1-((self.timer-FLASH_FREQUENCY)/FLASH_FREQUENCY)) + ) + else: + # Going from light to dark + get_node("Body").set_modulate( + lerp(FLASH_MIN, FLASH_MAX, self.timer / FLASH_FREQUENCY) + ) # Called every frame. 'delta' is the elapsed time since the previous frame. #func _process(delta): diff --git a/src/Square.tscn b/src/Square.tscn index 694fe04..3828a1a 100644 --- a/src/Square.tscn +++ b/src/Square.tscn @@ -19,6 +19,16 @@ modulate = Color( 0.12549, 0.0980392, 0.0980392, 1 ) texture = ExtResource( 1 ) [node name="Area2D" type="Area2D" parent="."] +visible = false [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] +visible = false shape = SubResource( 1 ) + +[node name="Control" type="Control" parent="."] +margin_right = 40.0 +margin_bottom = 40.0 +hint_tooltip = "Square" +__meta__ = { +"_edit_use_anchors_": false +}