This commit is contained in:
ProgramSnail 2023-08-09 23:12:19 +03:00
commit 5408d75267
2834 changed files with 377523 additions and 0 deletions

19
scripts/Camera.gd Normal file
View file

@ -0,0 +1,19 @@
extends Camera
export var speed = 10
export var zoom_speed = 0.6
func _process(delta):
if Input.is_action_pressed("camera_forward"):
global_translate(Vector3(-delta * speed, 0, 0))
if Input.is_action_pressed("camera_backward"):
global_translate(Vector3(delta * speed, 0, 0))
if Input.is_action_pressed("camera_left"):
global_translate(Vector3(0, 0, delta * speed))
if Input.is_action_pressed("camera_right"):
global_translate(Vector3(0, 0, -delta * speed))
if Input.is_action_just_released("zoom_in"):
translate(Vector3(0, 0, -zoom_speed))
if Input.is_action_just_released("zoom_out"):
translate(Vector3(0, 0, zoom_speed))
pass

39
scripts/Character.gd Normal file
View file

@ -0,0 +1,39 @@
extends Spatial
onready var animation_player = get_node("CharacterModel/AnimationPlayer")
onready var characters = [
get_node("CharacterModel/Rogue"),
get_node("CharacterModel/Worker"),
get_node("CharacterModel/Barbarian"),
get_node("CharacterModel/Knight"),
get_node("CharacterModel/Mage"),
]
export var ROGUE_LEVEL = 0
export var MAX_LEVEL = 4
var active = false
# character level, ROGUE_LEVEL for rogue
var level = 1
func set_character_visibility(character_is_visible : bool):
characters[level].visible = character_is_visible
func upgrade():
if level >= MAX_LEVEL: # MAX_LEVEL or special levels
return false
set_character_visibility(false)
level += 1
set_character_visibility(true)
return true
func became_rogue():
set_character_visibility(false)
level = ROGUE_LEVEL
set_character_visibility(true)
func _physics_process(_delta):
animation_player.play("Active" if active else "NotActive")

182
scripts/Characters.gd Normal file
View file

@ -0,0 +1,182 @@
extends Spatial
var character_scene = preload("res://scenes/character.tscn")
onready var map = get_node("/root/Level/Map")
onready var utils = get_node("/root/Level/Map/Utils")
onready var player_characters : Array = [[get_node("/root/Level/Map/Player0")], [], [], [], []]
onready var rogue_characters = []
var current_player = 0
var current_character = 0
var current_player_used_characters = {}
func is_current_player_character_used(character : int):
return current_player_used_characters.has(character)
func current_character_obj():
return player_characters[current_player][current_character]
func current_player_characters_count():
return player_characters[current_player].size()
func is_current_character_exist():
return current_character < current_player_characters_count()
func spawn_current_player_character(position : Vector3):
player_characters[current_player].append( \
utils.spawn_on_position(character_scene, position))
func destroy_characters_on_position(characters : Array, position : Vector3):
var destroyed_count = 0
var alive = []
for character in characters:
var character_position = utils.world_to_grid_position(character.translation)
if character_position.is_equal_approx(position):
destroyed_count += 1
character.queue_free()
else:
alive.append(character)
return [alive, destroyed_count]
func destroy_other_player_characters(position : Vector3, skipped_player : int):
var characters_destroyed_count = 0
for player in range(0, player_characters.size()):
if player == skipped_player:
continue
var player_destruction_result = \
destroy_characters_on_position(player_characters[player], position)
player_characters[player] = player_destruction_result[0]
characters_destroyed_count += player_destruction_result[1]
var rogue_destruction_result = \
destroy_characters_on_position(rogue_characters, position)
rogue_characters = rogue_destruction_result[0]
characters_destroyed_count += rogue_destruction_result[1]
return characters_destroyed_count
# not optimal
func turn_characters_into_rogues():
var new_rogue_characters = 0
for player in range(0, player_characters.size()):
var alive_characters = []
for character in player_characters[player]:
var character_position = utils.world_to_grid_position(character.translation)
if not map.is_connected_to_house(character_position, map.player_tiles[player]):
new_rogue_characters += 1
character.became_rogue()
rogue_characters.append(character)
map.set_tile_cell(character_position, map.EMPTY_TILE)
else:
alive_characters.append(character)
player_characters[player] = alive_characters
return new_rogue_characters
# =< 1 characters on one position
func find_character_and_player_ids_on(position : Vector3):
for player in range(player_characters.size()):
for character in range(player_characters[player].size()):
var character_position = \
utils.world_to_grid_position(player_characters[player][character].translation)
if character_position.is_equal_approx(position):
return [player, character]
return null
# =< 1 characters on one position
func find_character_on(position : Vector3):
var character_and_player_ids = find_character_and_player_ids_on(position)
if character_and_player_ids == null:
return null
return player_characters[character_and_player_ids[0]][character_and_player_ids[1]]
func set_next_character():
if current_character < current_player_characters_count():
current_character_obj().active = false
current_player_used_characters[current_character] = null
if current_player_used_characters.size() < current_player_characters_count():
for i in range(current_player_characters_count()):
if not current_player_used_characters.has(i):
current_character = i
else:
if current_character == current_player_characters_count():
current_character += 1
else:
current_character = current_player_characters_count()
if current_character >= current_player_characters_count() + 1:
set_next_player()
if current_character < current_player_characters_count():
current_character_obj().active = true
func set_next_player():
if current_character < current_player_characters_count():
current_character_obj().active = false
map.player_tiles[current_player] = map.DEFAULT_PLAYER_TILES[current_player]
print(map.player_tiles[current_player])
print(map.DEFAULT_PLAYER_TILES[current_player])
map.remove_active_player_color(current_player)
current_player_used_characters.clear()
current_player += 1
if current_player >= map.PLAYERS_COUNT:
current_player %= map.PLAYERS_COUNT
map.set_active_player_color(current_player)
map.player_tiles[current_player] = map.ACTIVE_PLAYER_TILES[current_player]
map.start_player_turn(current_player)
current_character = 0
if current_character < current_player_characters_count():
current_character_obj().active = true
func switch_character_to(player : int, character : int):
if current_character < current_player_characters_count():
current_character_obj().active = false
current_player = player
current_character = character
if current_character < current_player_characters_count():
current_character_obj().active = true
# not optiomal
func characters_tile_block_level(position : Vector3, inviding_player : int):
var block_level = -1
for player in range(0, player_characters.size()):
for character in player_characters[player]:
var character_position = utils.world_to_grid_position(character.translation)
if player != inviding_player and \
(map.is_neighbour_tiles(character_position, position) or \
character_position.is_equal_approx(position)) and \
map.get_tile_cell(position) == map.player_tiles[player]:
block_level = max(block_level, character.level)
if player == inviding_player and \
character_position.is_equal_approx(position):
block_level = character.MAX_LEVEL
return block_level

12
scripts/Cursor.gd Normal file
View file

@ -0,0 +1,12 @@
extends MeshInstance
onready var camera : Camera = get_node("/root/Level/Camera")
func _physics_process(_delta):
var position_2d = get_viewport().get_mouse_position()
var drop_plane = Plane(Vector3(0, 1, 0), 1)
var position_3d = drop_plane.intersects_ray(
camera.project_ray_origin(position_2d),
camera.project_ray_normal(position_2d))
translation = position_3d

8
scripts/Destruction.gd Normal file
View file

@ -0,0 +1,8 @@
extends Particles
func _ready():
emitting = true
func _physics_process(_delta):
if not emitting:
queue_free()

8
scripts/HouseInfo.gd Normal file
View file

@ -0,0 +1,8 @@
extends Spatial
onready var info = $Info
var money = 0
func _process(_delta):
info.text = "Money: " + str(money)

206
scripts/Map.gd Normal file
View file

@ -0,0 +1,206 @@
extends Spatial
# TODO: create something like Vector3i
var building_destruction_scene = preload("res://scenes/destruction.tscn")
onready var characters = get_node("/root/Level/Map/Characters")
onready var money = get_node("/root/Level/Map/Money")
onready var utils = get_node("/root/Level/Map/Utils")
onready var tiles : GridMap = get_node("/root/Level/Map/Tiles")
onready var buildings : GridMap = get_node("/root/Level/Map/Buildings")
export var EMPTY_TILE = 0
export var PLAYERS_COUNT = 2
export var DEFAULT_PLAYER_TILES = [1, 2, 3, 4, 5]
export var ACTIVE_PLAYER_TILES = [6, 7, 8, 9, 10]
var player_tiles = []
export var HOUSE_CELLS = [0, 1, 2, 11, 12, 13, 14, 16, 19] # 14, 19 - mills
export var TOWER_CELLS = [2, 18]
export var HOUSE_DEFENCE_LEVEL = 1
export var TOWER_DEFENCE_LEVEL = 2
const directions = [
Vector3(2, 0, 0),
Vector3(-2, 0, 0),
Vector3(1, 0, 2),
Vector3(-1, 0, 2),
Vector3(1, 0, -2),
Vector3(-1, 0, -2)
]
func get_tile_cell(position : Vector3):
return tiles.get_cell_item(int(round(position.x)), int(round(position.y)), int(round(position.z)))
func set_tile_cell(position : Vector3, cell : int):
tiles.set_cell_item(int(round(position.x)), int(round(position.y)), int(round(position.z)), cell)
func get_building_cell(position : Vector3):
return buildings.get_cell_item(int(round(position.x)), int(round(position.y)), int(round(position.z)))
func set_building_cell(position : Vector3, cell : int):
buildings.set_cell_item(int(round(position.x)), int(round(position.y)), int(round(position.z)), cell)
func has_neighbour_tile_cell(position : Vector3, required_neighbour_tile_cell : int):
for direction in directions:
var neighbour_position = direction + position
if get_tile_cell(neighbour_position) == required_neighbour_tile_cell:
return true
return false
func has_building_defence(position : Vector3, building_cell : int, skipped_player : int):
var current_cell = get_tile_cell(position)
for direction in directions:
var neighbour_position = direction + position
var neighbour_cell = get_tile_cell(neighbour_position)
if current_cell == neighbour_cell and \
neighbour_cell != player_tiles[skipped_player] and \
get_building_cell(neighbour_position) == building_cell:
return true
return get_building_cell(position) == building_cell and current_cell != player_tiles[skipped_player]
func is_neighbour_tiles(position : Vector3, other_position : Vector3):
for direction in directions:
if direction.is_equal_approx(other_position - position):
return true
return false
func start_player_turn(player : int):
money.update_houses_money(player)
money.pay_tower_salaries(player)
money.pay_character_salaries(player)
func are_tiles_connected(start_position : Vector3, end_position : Vector3, between_tile_cell : int):
if start_position.is_equal_approx(end_position):
return true
var visited = {}
var to_visit = [start_position]
visited[utils.position_to_string(start_position)] = null
for current_position in to_visit:
for direction in directions:
if (current_position + direction).is_equal_approx(end_position):
return true
if get_tile_cell(current_position + direction) == between_tile_cell and \
not visited.has(utils.position_to_string(current_position + direction)):
visited[utils.position_to_string(current_position + direction)] = null
to_visit.append(current_position + direction)
return false
func is_connected_to_house(start_position : Vector3, tile_cell : int):
var visited = {}
var to_visit = [start_position]
visited[utils.position_to_string(start_position)] = null
for current_position in to_visit:
if HOUSE_CELLS.has(get_building_cell(current_position)):
return true
for direction in directions:
if get_tile_cell(current_position + direction) == tile_cell and \
not visited.has(utils.position_to_string(current_position + direction)):
visited[utils.position_to_string(current_position + direction)] = null
to_visit.append(current_position + direction)
return false
func tile_block_level(position : Vector3, inviding_player : int):
var block_level = -1
block_level = characters.characters_tile_block_level(position, inviding_player)
for house_cell in HOUSE_CELLS:
if has_building_defence(position, house_cell, inviding_player):
block_level = max(block_level, HOUSE_DEFENCE_LEVEL)
break
for tower_cell in TOWER_CELLS:
if has_building_defence(position, tower_cell, inviding_player):
block_level = max(block_level, TOWER_DEFENCE_LEVEL)
return block_level
func move_to_tile(position : Vector3):
var tile_cell = get_tile_cell(position)
var building_cell = get_building_cell(position)
var world_position = utils.grid_to_world_position(position)
# tile to move should be connected with current character tile
if not are_tiles_connected(utils.world_to_grid_position(characters.current_character_obj().translation), position, current_player_tile()):
return
# can't go on tile, blocked with >= level, except characters with MAX_LEVEL
if tile_block_level(position, characters.current_player) >= characters.current_character_obj().level and \
characters.current_character_obj().level != characters.current_character_obj().MAX_LEVEL:
return
if tile_cell == GridMap.INVALID_CELL_ITEM:
return
# move without action (to current player tile)
if tile_cell == current_player_tile() and \
building_cell == GridMap.INVALID_CELL_ITEM:
if characters.destroy_other_player_characters(position, characters.current_player) > 0:
utils.spawn_on_position(building_destruction_scene, position)
characters.current_character_obj().translation = world_position
# move with action (to tile of other player)
elif tile_cell != GridMap.INVALID_CELL_ITEM and \
tile_cell != current_player_tile() and \
has_neighbour_tile_cell(position, current_player_tile()):
set_tile_cell(position, current_player_tile())
if building_cell != GridMap.INVALID_CELL_ITEM or \
characters.destroy_other_player_characters(position, characters.current_player) > 0:
utils.spawn_on_position(building_destruction_scene, position)
if building_cell != GridMap.INVALID_CELL_ITEM:
money.destroy_house(position)
set_building_cell(position, GridMap.INVALID_CELL_ITEM)
characters.current_character_obj().translation = world_position
characters.turn_characters_into_rogues()
characters.set_next_character()
func current_player_tile():
return player_tiles[characters.current_player]
func remove_active_player_color(player : int):
print("Player tile: ", player_tiles[player])
for tile_position in tiles.get_used_cells():
if get_tile_cell(tile_position) == ACTIVE_PLAYER_TILES[player]:
print("Active tile removed")
set_tile_cell(tile_position, DEFAULT_PLAYER_TILES[player])
func set_active_player_color(player : int):
for tile_position in tiles.get_used_cells():
if get_tile_cell(tile_position) == DEFAULT_PLAYER_TILES[player]:
set_tile_cell(tile_position, ACTIVE_PLAYER_TILES[player])
func _ready():
if characters.is_current_character_exist():
characters.current_character_obj().active = true
player_tiles = DEFAULT_PLAYER_TILES.duplicate()
set_active_player_color(characters.current_player)
player_tiles[characters.current_player] = ACTIVE_PLAYER_TILES[characters.current_player]
money.init_houses()

134
scripts/Money.gd Normal file
View file

@ -0,0 +1,134 @@
extends Spatial
var house_info_scene = preload("res://scenes/house_info.tscn")
onready var map = get_node("/root/Level/Map")
onready var characters = get_node("/root/Level/Map/Characters")
onready var utils = get_node("/root/Level/Map/Utils")
onready var buildings = get_node("/root/Level/Map/Buildings")
export var CHARACTER_COST = 10
# first is rogue, last upgrade is impossible
export var CHARACTER_UPGRADE_COSTS = [0, 10, 20, 40, 80]
export var CHARACTER_SALARIES = [0, 2, 6, 18, 54]
export var TOWER_SALARY = 2
export var HOUSE_STARTING_MONEY = 10
export var TILE_MONEY = 1
var houses_money = {}
var houses_info = {}
func pay_tower_salaries(player : int):
for tower_position in buildings.get_used_cells():
if map.TOWER_CELLS.has(map.get_building_cell(tower_position)) and \
map.get_tile_cell(tower_position) == map.player_tiles[player]:
spend_connected_money(tower_position, map.player_tiles[player], TOWER_SALARY)
func pay_character_salaries(player : int):
var alive_characters = []
for character in characters.player_characters[player]:
var character_position = utils.world_to_grid_position(character.translation)
if spend_connected_money(character_position, map.player_tiles[player], CHARACTER_SALARIES[character.level]):
alive_characters.append(character)
else:
character.became_rogue()
characters.rogue_characters.append(character)
characters.player_characters[player] = alive_characters
func count_connected_money(starting_position : Vector3, tile_cell : int):
var connected_money = 0
var visited = {}
var to_visit = [starting_position]
visited[utils.position_to_string(starting_position)] = null
for current_position in to_visit:
if map.HOUSE_CELLS.has(map.get_building_cell(current_position)):
connected_money += houses_money[utils.position_to_string(current_position)]
for direction in map.directions:
if map.get_tile_cell(current_position + direction) == tile_cell and \
not visited.has(utils.position_to_string(current_position + direction)):
visited[utils.position_to_string(current_position + direction)] = null
to_visit.append(current_position + direction)
return connected_money
func spend_connected_money(starting_position : Vector3, tile_cell : int, money_to_spend : int):
if money_to_spend <= 0:
return true
var visited = {}
var to_visit = [starting_position]
visited[utils.position_to_string(starting_position)] = null
for current_position in to_visit:
if map.HOUSE_CELLS.has(map.get_building_cell(current_position)):
var money_in_house = houses_money[utils.position_to_string(current_position)]
houses_money[utils.position_to_string(current_position)] = max(0, money_in_house - money_to_spend)
update_house_info(current_position)
money_to_spend -= money_in_house
if money_to_spend <= 0:
return true
for direction in map.directions:
if map.get_tile_cell(current_position + direction) == tile_cell and \
not visited.has(utils.position_to_string(current_position + direction)):
visited[utils.position_to_string(current_position + direction)] = null
to_visit.append(current_position + direction)
return money_to_spend <= 0
func update_house_info(position : Vector3):
var position_string = utils.position_to_string(position)
houses_info[position_string].money = houses_money[position_string]
func update_houses_money(player : int):
var rogue_positions = {}
for rogue in characters.rogue_characters:
rogue_positions[utils.position_to_string(utils.world_to_grid_position(rogue.translation))] = null
var visited = {}
var to_visit = []
for building_position in buildings.get_used_cells():
if map.HOUSE_CELLS.has(map.get_building_cell(building_position)) and \
map.get_tile_cell(building_position) == map.player_tiles[player]:
visited[utils.position_to_string(building_position)] = null
to_visit.append([building_position, building_position])
for current in to_visit:
if not rogue_positions.has(utils.position_to_string(current[0])):
houses_money[utils.position_to_string(current[1])] += TILE_MONEY
update_house_info(current[1])
for direction in map.directions:
if map.get_tile_cell(current[0] + direction) == map.player_tiles[player] and \
not visited.has(utils.position_to_string(current[0] + direction)):
visited[utils.position_to_string(current[0] + direction)] = null
to_visit.append([current[0] + direction, current[1]])
func destroy_house(position : Vector3):
var position_string = utils.position_to_string(position)
houses_money.erase(position_string)
houses_info[position_string].queue_free()
houses_info.erase(position_string)
func init_houses():
for building_position in buildings.get_used_cells():
if map.HOUSE_CELLS.has(map.get_building_cell(building_position)):
var building_position_string = utils.position_to_string(building_position)
houses_money[building_position_string] = HOUSE_STARTING_MONEY
houses_info[building_position_string] = utils.spawn_on_position(house_info_scene, building_position)
update_house_info(building_position)

54
scripts/PlayerActions.gd Normal file
View file

@ -0,0 +1,54 @@
extends Spatial
onready var map = get_node("/root/Level/Map")
onready var characters = get_node("/root/Level/Map/Characters")
onready var money = get_node("/root/Level/Map/Money")
onready var utils = get_node("/root/Level/Map/Utils")
func make_turn(position : Vector3):
if map.is_connected_to_house(position, map.current_player_tile()):
var mouse_position_character_and_player_ids = characters.find_character_and_player_ids_on(position)
if map.get_tile_cell(position) == map.current_player_tile() and \
mouse_position_character_and_player_ids != null and \
mouse_position_character_and_player_ids[0] == characters.current_player and \
not characters.is_current_player_character_used(mouse_position_character_and_player_ids[1]):
characters.switch_character_to(mouse_position_character_and_player_ids[0],
mouse_position_character_and_player_ids[1])
elif characters.is_current_character_exist():
map.move_to_tile(position)
func upgrade_character_on_position(position : Vector3):
var character_on_position = characters.find_character_on(position)
if character_on_position != null and \
map.get_tile_cell(position) == map.current_player_tile() and \
money.count_connected_money(position, map.current_player_tile()) \
>= money.CHARACTER_UPGRADE_COSTS[character_on_position.level] and \
character_on_position.upgrade():
money.spend_connected_money(position, map.current_player_tile(), money.CHARACTER_COST)
func try_spawn_character(position : Vector3):
if map.get_tile_cell(position) == map.current_player_tile() and \
map.get_building_cell(position) == GridMap.INVALID_CELL_ITEM and \
map.is_connected_to_house(position, map.current_player_tile()) and \
characters.find_character_and_player_ids_on(position) == null and \
money.count_connected_money(position, map.current_player_tile()) >= money.CHARACTER_COST:
money.spend_connected_money(position, map.current_player_tile(), money.CHARACTER_COST)
characters.spawn_current_player_character(position)
if characters.current_character + 1 == characters.current_player_characters_count():
characters.current_character_obj().active = true
func _physics_process(_delta):
if Input.is_action_just_released("turn"):
make_turn(utils.world_to_grid_position(utils.mouse_position()))
if Input.is_action_just_released("upgrade"):
upgrade_character_on_position(utils.world_to_grid_position(utils.mouse_position()))
if Input.is_action_just_released("pass"):
characters.set_next_player()
if Input.is_action_just_released("spawn"):
try_spawn_character(utils.world_to_grid_position(utils.mouse_position()))

44
scripts/Utils.gd Normal file
View file

@ -0,0 +1,44 @@
extends Spatial
onready var tiles : GridMap = get_node("/root/Level/Map/Tiles")
onready var camera : Camera = get_node("/root/Level/Camera")
func mouse_position():
var position_2d = get_viewport().get_mouse_position()
var drop_plane = Plane(Vector3(0, 1, 0), 1)
var position_3d = drop_plane.intersects_ray(
camera.project_ray_origin(position_2d),
camera.project_ray_normal(position_2d))
return position_3d
func position_to_string(position : Vector3):
return str([int(round(position.x)), int(round(position.y)), int(round(position.z))])
export var TILE_OFFSET : Vector3 = Vector3(0.5, 0, 0.5)
func world_to_grid_position(position : Vector3):
position += Vector3(TILE_OFFSET.x * tiles.cell_size.x,
TILE_OFFSET.y * tiles.cell_size.y,
TILE_OFFSET.z * tiles.cell_size.z)
var diagonal_steps_amount = position.z / (2 * tiles.cell_size.z)
var forward_steps_amount = (position.x - diagonal_steps_amount * tiles.cell_size.x) \
/ (2 * tiles.cell_size.x)
var hex_ceil_z = int(round(diagonal_steps_amount))
var hex_ceil_x = int(round(forward_steps_amount + hex_ceil_z / 2))
var row_is_moved = (int(abs(hex_ceil_z)) % 2 == 1)
return Vector3(int(hex_ceil_x * 2 + (sign(hex_ceil_z) if row_is_moved else 0.0) - 1), 0, int(hex_ceil_z * 2 - 1))
func grid_to_world_position(position : Vector3):
return tiles.map_to_world(int(round(position.x)), int(round(position.y)), int(round(position.z)))
func spawn_on_position(scene, world_position : Vector3):
var instance = scene.instance()
add_child(instance)
instance.translation = grid_to_world_position(world_position)
return instance