game: add enemy mods
This commit is contained in:
@@ -24,7 +24,7 @@ const GROUP_XP_ORB = "xp_orb"
|
||||
const GROUP_PICKUP = "pickup"
|
||||
const GROUP_PROJ_MANAGER = "proj_manager"
|
||||
|
||||
enum ModRarity { LEGENDARY, EPIC, RARE, NORMAL }
|
||||
enum Rarity { LEGENDARY, EPIC, RARE, NORMAL }
|
||||
|
||||
const placeholder_tex = preload("res://assets/sprites/64x64_placeholder.tres")
|
||||
|
||||
@@ -32,7 +32,7 @@ var MOD_CHOICES = [
|
||||
{
|
||||
"internal_name": "flat_health_small",
|
||||
"name": "+10 Health",
|
||||
"rarity": ModRarity.NORMAL,
|
||||
"rarity": Rarity.NORMAL,
|
||||
"tex": placeholder_tex,
|
||||
"description": "Adds 10 flat health.",
|
||||
"weight": 100,
|
||||
@@ -43,7 +43,7 @@ var MOD_CHOICES = [
|
||||
{
|
||||
"internal_name": "flat_health_med",
|
||||
"name": "+25 Health",
|
||||
"rarity": ModRarity.RARE,
|
||||
"rarity": Rarity.RARE,
|
||||
"tex": placeholder_tex,
|
||||
"description": "Adds 25 flat health.",
|
||||
"weight": 50,
|
||||
@@ -54,7 +54,7 @@ var MOD_CHOICES = [
|
||||
{
|
||||
"internal_name": "flat_health_large",
|
||||
"name": "+50 Health",
|
||||
"rarity": ModRarity.EPIC,
|
||||
"rarity": Rarity.EPIC,
|
||||
"tex": placeholder_tex,
|
||||
"description": "Adds 50 flat health.",
|
||||
"weight": 10,
|
||||
@@ -65,7 +65,7 @@ var MOD_CHOICES = [
|
||||
{
|
||||
"internal_name": "flat_crit_small",
|
||||
"name": "2.5% More Critical Chance",
|
||||
"rarity": ModRarity.RARE,
|
||||
"rarity": Rarity.RARE,
|
||||
"tex": placeholder_tex,
|
||||
"description": "Gives 2.5% more base critical hit chance",
|
||||
"weight": 50,
|
||||
@@ -76,7 +76,7 @@ var MOD_CHOICES = [
|
||||
{
|
||||
"internal_name": "flat_crit_large",
|
||||
"name": "5% More Critical Chance",
|
||||
"rarity": ModRarity.EPIC,
|
||||
"rarity": Rarity.EPIC,
|
||||
"tex": placeholder_tex,
|
||||
"description": "Gives 5% more base critical hit chance",
|
||||
"weight": 10,
|
||||
@@ -87,6 +87,17 @@ var MOD_CHOICES = [
|
||||
]
|
||||
|
||||
|
||||
func rarity_to_color(rarity: Rarity) -> Color:
|
||||
match rarity:
|
||||
Rarity.RARE:
|
||||
return Color.YELLOW
|
||||
Rarity.EPIC:
|
||||
return Color.BLUE_VIOLET
|
||||
Rarity.LEGENDARY:
|
||||
return Color.DARK_ORANGE
|
||||
return Color.WHITE
|
||||
|
||||
|
||||
func _draw_random_choice(fortune: float = 1.0) -> Dictionary:
|
||||
var total_weight: int = 0
|
||||
for choice in MOD_CHOICES:
|
||||
|
@@ -9,9 +9,9 @@ extends CharacterBody2D
|
||||
@onready var contact_damage_cd: Timer = $ContactDamageCD
|
||||
@onready var animation_player: AnimationPlayer = $AnimationPlayer
|
||||
|
||||
var move_speed: float
|
||||
var health: float
|
||||
var max_health: float
|
||||
var move_speed: float = default_move_speed
|
||||
var health: float = default_max_health
|
||||
var max_health: float = default_max_health
|
||||
var god_mode: bool = false
|
||||
var is_dead: bool = false
|
||||
|
||||
|
@@ -7,6 +7,7 @@ extends CharacterBody2D
|
||||
@export var target_distance: float = 6.0
|
||||
@export var path_update_interval: float = 1.5
|
||||
@export var xp_dropped: float = 5.0
|
||||
@export var enemy_rarity: GlobalConst.Rarity = GlobalConst.Rarity.NORMAL
|
||||
var modifiers: Array[EnemyMod] = []
|
||||
|
||||
@onready var target_cast: RayCast2D = $TargetCast
|
||||
@@ -16,6 +17,7 @@ var modifiers: Array[EnemyMod] = []
|
||||
@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D
|
||||
@onready var shape_cast_2d: ShapeCast2D = $ShapeCast2D
|
||||
@onready var sprite_2d: Sprite2D = $Sprite2D
|
||||
@onready var label: Label = $Label
|
||||
|
||||
var player: Player
|
||||
var enemy_name: String
|
||||
@@ -28,8 +30,23 @@ var _path_update_timer: float = 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
health = max_health
|
||||
enemy_name = _gen_name()
|
||||
match enemy_rarity:
|
||||
GlobalConst.Rarity.NORMAL:
|
||||
label.visible = false
|
||||
GlobalConst.Rarity.RARE:
|
||||
var mods = EnemyModPool.get_random_mods(2)
|
||||
label.visible = true
|
||||
modifiers = mods
|
||||
GlobalConst.Rarity.EPIC:
|
||||
var mods = EnemyModPool.get_random_mods(4)
|
||||
label.visible = true
|
||||
modifiers = mods
|
||||
if modifiers.size() > 0:
|
||||
enemy_name += " the %s" % modifiers.pick_random().adjective
|
||||
label.add_theme_color_override("font_color", GlobalConst.rarity_to_color(enemy_rarity))
|
||||
label.text = enemy_name
|
||||
health = get_calculated("max_health")
|
||||
shape_cast_2d.shape.radius = collision_shape_2d.shape.radius
|
||||
shape_cast_2d.enabled = false
|
||||
sprite_2d.material = sprite_2d.material.duplicate()
|
||||
@@ -80,7 +97,7 @@ func _do_simple_movement():
|
||||
var direction = global_position.direction_to(target.global_position)
|
||||
var distance = global_position.distance_to(target.global_position)
|
||||
if distance > 4:
|
||||
velocity = direction * move_speed
|
||||
velocity = direction * get_calculated("move_speed")
|
||||
move_and_slide()
|
||||
|
||||
|
||||
@@ -98,7 +115,7 @@ func _do_nav_agent_movement():
|
||||
if shape_cast_2d.is_colliding():
|
||||
direction = direction.bounce(shape_cast_2d.get_collision_normal(0)).normalized()
|
||||
|
||||
velocity = direction * move_speed
|
||||
velocity = direction * get_calculated("move_speed")
|
||||
move_and_slide()
|
||||
|
||||
|
||||
@@ -148,7 +165,7 @@ func die():
|
||||
|
||||
func drop_xp_orb() -> void:
|
||||
var orb: XPOrb = preload("res://scenes/xp_orb.tscn").instantiate()
|
||||
orb.value = xp_dropped
|
||||
orb.value = xp_dropped * (1 + (modifiers.size() * 3))
|
||||
orb.position = position
|
||||
get_parent().call_deferred("add_child", orb)
|
||||
|
||||
@@ -156,3 +173,21 @@ func drop_xp_orb() -> void:
|
||||
func _on_animation_player_animation_finished(anim_name: StringName) -> void:
|
||||
if is_dead:
|
||||
queue_free()
|
||||
|
||||
|
||||
func get_calculated(key: String) -> Variant:
|
||||
# set max move speed to players move speed
|
||||
if key == "move_speed":
|
||||
return clampf(
|
||||
EnemyMod.get_calculated(self, key),
|
||||
0,
|
||||
player.player_stats.get_final("move_speed", player.modifiers)
|
||||
)
|
||||
return EnemyMod.get_calculated(self, key)
|
||||
|
||||
|
||||
func has_property(key: String) -> bool:
|
||||
for prop in get_property_list():
|
||||
if prop.name == key:
|
||||
return true
|
||||
return false
|
||||
|
@@ -90,4 +90,20 @@ wait_time = 0.5
|
||||
shape = SubResource("CircleShape2D_pkqou")
|
||||
max_results = 2
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -85.5
|
||||
offset_top = 5.0
|
||||
offset_right = 85.5
|
||||
offset_bottom = 28.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
theme_override_font_sizes/font_size = 9
|
||||
text = "Unnamed the Adjective"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[connection signal="animation_finished" from="AnimationPlayer" to="." method="_on_animation_player_animation_finished"]
|
||||
|
@@ -26,4 +26,7 @@ const NAME_SUFFIXES: Array[String] = [
|
||||
|
||||
|
||||
func _gen_name() -> String:
|
||||
return "%s%s%s" % [NAME_PREFIXES.pick_random(), NAME_ROOTS.pick_random(), NAME_SUFFIXES.pick_random()]
|
||||
return (
|
||||
"%s%s%s"
|
||||
% [NAME_PREFIXES.pick_random(), NAME_ROOTS.pick_random(), NAME_SUFFIXES.pick_random()]
|
||||
)
|
||||
|
@@ -3,13 +3,20 @@ extends Resource
|
||||
|
||||
enum ModType { ADDITIVE, MULTIPLICATIVE, ABSOLUTE, BOOL }
|
||||
|
||||
@export var mod_name: String
|
||||
@export var mod_property: String
|
||||
@export var mod_value: float
|
||||
@export var mod_value_bool: bool = false
|
||||
@export var mod_type: ModType = ModType.MULTIPLICATIVE
|
||||
|
||||
func get_calculated(enemy: EnemyBase, key: String) -> Variant:
|
||||
assert(enemy.has_property(key), "tried to calculate property '%s' where base value does not exist on %s" % [key, enemy])
|
||||
var adjective: String
|
||||
|
||||
|
||||
static func get_calculated(enemy: EnemyBase, key: String) -> Variant:
|
||||
assert(
|
||||
enemy.has_property(key),
|
||||
"tried to calculate property '%s' where base value does not exist on %s" % [key, enemy]
|
||||
)
|
||||
var base_value = enemy.get(key)
|
||||
var add = 0.0
|
||||
var mul = 1.0
|
||||
@@ -17,11 +24,11 @@ func get_calculated(enemy: EnemyBase, key: String) -> Variant:
|
||||
if mod.mod_property == key:
|
||||
match mod.mod_type:
|
||||
ModType.ADDITIVE:
|
||||
add += mod.mod_value
|
||||
add += mod.mod_value
|
||||
ModType.MULTIPLICATIVE:
|
||||
mul *= mod.mod_value
|
||||
mul *= mod.mod_value
|
||||
ModType.ABSOLUTE:
|
||||
return mod.mod_value
|
||||
return mod.mod_value
|
||||
ModType.BOOL:
|
||||
return mod.mod_value_bool
|
||||
return mod.mod_value_bool
|
||||
return (base_value + add) * mul
|
||||
|
58
scenes/enemies/enemy_mod_pool.gd
Normal file
58
scenes/enemies/enemy_mod_pool.gd
Normal file
@@ -0,0 +1,58 @@
|
||||
class_name EnemyModPool
|
||||
extends Resource
|
||||
|
||||
const MAX_HEALTH_MOD: Dictionary = {
|
||||
"mod_name": "Extra health",
|
||||
"mod_property": "max_health",
|
||||
"mod_value": 1.0,
|
||||
"mod_value_bool": false,
|
||||
"mod_type": EnemyMod.ModType.MULTIPLICATIVE,
|
||||
"mod_adjectives":
|
||||
[
|
||||
"Sturdy",
|
||||
"Bulky",
|
||||
"Ironclad",
|
||||
"Unyielding",
|
||||
"Stonehide",
|
||||
"Thicc",
|
||||
"Chunky",
|
||||
"Pudgy",
|
||||
"Chonky",
|
||||
"Double-stuffed"
|
||||
],
|
||||
}
|
||||
|
||||
const MOD_POOL = [
|
||||
{
|
||||
"mod_name": "Extra movement speed",
|
||||
"mod_property": "move_speed",
|
||||
"mod_value": 1.25,
|
||||
"mod_value_bool": false,
|
||||
"mod_type": EnemyMod.ModType.MULTIPLICATIVE,
|
||||
"mod_adjectives": ["Swift", "Nimble", "Fleet", "Spry", "Skittering", "Zoomy", "Zippy"]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
static func get_random_mods(count: int) -> Array[EnemyMod]:
|
||||
var mods: Array[EnemyMod] = []
|
||||
# always include extra health mod
|
||||
var hp_mod = _dict_to_mod(MAX_HEALTH_MOD)
|
||||
hp_mod.mod_value *= 5 * count
|
||||
mods.append(hp_mod)
|
||||
for i in count:
|
||||
var mod_data = MOD_POOL.pick_random()
|
||||
var mod = _dict_to_mod(mod_data)
|
||||
mods.append(mod)
|
||||
return mods
|
||||
|
||||
|
||||
static func _dict_to_mod(d: Dictionary) -> EnemyMod:
|
||||
var mod: EnemyMod = EnemyMod.new()
|
||||
mod.mod_name = d["mod_name"]
|
||||
mod.mod_property = d["mod_property"]
|
||||
mod.mod_value = d["mod_value"]
|
||||
mod.mod_value_bool = d["mod_value_bool"]
|
||||
mod.mod_type = d["mod_type"]
|
||||
mod.adjective = d["mod_adjectives"].pick_random()
|
||||
return mod
|
1
scenes/enemies/enemy_mod_pool.gd.uid
Normal file
1
scenes/enemies/enemy_mod_pool.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bl11yfav37ecs
|
@@ -19,14 +19,11 @@ const NAME_ROOTS: Array[String] = [
|
||||
"uzzle",
|
||||
]
|
||||
|
||||
const NAME_SUFFIXES: Array[String] = [
|
||||
"y",
|
||||
"er",
|
||||
"o",
|
||||
"ok",
|
||||
"in"
|
||||
]
|
||||
const NAME_SUFFIXES: Array[String] = ["y", "er", "o", "ok", "in"]
|
||||
|
||||
|
||||
func _gen_name() -> String:
|
||||
return "%s%s%s" % [NAME_PREFIXES.pick_random(), NAME_ROOTS.pick_random(), NAME_SUFFIXES.pick_random()]
|
||||
return (
|
||||
"%s%s%s"
|
||||
% [NAME_PREFIXES.pick_random(), NAME_ROOTS.pick_random(), NAME_SUFFIXES.pick_random()]
|
||||
)
|
||||
|
@@ -39,16 +39,22 @@ const NAME_SUFFIXES: Array[String] = [
|
||||
"oo",
|
||||
]
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
shader_material = ShaderMaterial.new()
|
||||
disabled_sprite.visible = false
|
||||
set_color(color)
|
||||
shader_material.shader = shader
|
||||
base_sprite.material = shader_material
|
||||
super._ready()
|
||||
|
||||
|
||||
func set_color(new_color: Color) -> void:
|
||||
shader_material.set_shader_parameter("base_color", new_color)
|
||||
|
||||
|
||||
func _gen_name() -> String:
|
||||
return "%s%s%s" % [NAME_PREFIXES.pick_random(), NAME_ROOTS.pick_random(), NAME_SUFFIXES.pick_random()]
|
||||
return (
|
||||
"%s%s%s"
|
||||
% [NAME_PREFIXES.pick_random(), NAME_ROOTS.pick_random(), NAME_SUFFIXES.pick_random()]
|
||||
)
|
||||
|
@@ -37,6 +37,10 @@ func _on_timer_timeout() -> void:
|
||||
var new_enemy = next_enemy.instantiate()
|
||||
new_enemy.position = _get_spawn_pos()
|
||||
new_enemy.target = target
|
||||
if randf() < 0.1:
|
||||
new_enemy.enemy_rarity = GlobalConst.Rarity.RARE
|
||||
if randf() < 0.1:
|
||||
new_enemy.enemy_rarity = GlobalConst.Rarity.EPIC
|
||||
if is_instance_of(new_enemy, EnemySlimeSmall):
|
||||
var slime_color: Color = SLIME_COLOR_VARIATIONS.pick_random()
|
||||
new_enemy.color = slime_color
|
||||
|
@@ -13,13 +13,13 @@ signal lvlup_picked(mod: PlayerStatsModifier)
|
||||
|
||||
func _ready() -> void:
|
||||
match mod.rarity:
|
||||
GlobalConst.ModRarity.NORMAL:
|
||||
GlobalConst.Rarity.NORMAL:
|
||||
upgrade_name.add_theme_color_override("font_color", Color.WHITE)
|
||||
GlobalConst.ModRarity.RARE:
|
||||
GlobalConst.Rarity.RARE:
|
||||
upgrade_name.add_theme_color_override("font_color", Color.DODGER_BLUE)
|
||||
GlobalConst.ModRarity.EPIC:
|
||||
GlobalConst.Rarity.EPIC:
|
||||
upgrade_name.add_theme_color_override("font_color", Color.DARK_ORCHID)
|
||||
GlobalConst.ModRarity.LEGENDARY:
|
||||
GlobalConst.Rarity.LEGENDARY:
|
||||
upgrade_name.add_theme_color_override("font_color", Color.DARK_ORANGE)
|
||||
|
||||
upgrade_name.text = mod.title
|
||||
|
@@ -16,7 +16,7 @@ func pickup() -> void:
|
||||
mod.title = "Magnet"
|
||||
mod.tex = GlobalConst.placeholder_tex
|
||||
mod.internal_name = "magnet"
|
||||
mod.rarity = GlobalConst.ModRarity.RARE
|
||||
mod.rarity = GlobalConst.Rarity.RARE
|
||||
mod.stat_name = "pickup_radius"
|
||||
mod.value = 5000.0
|
||||
mod.type = PlayerStatsModifier.ModifierType.ABSOLUTE
|
||||
|
@@ -8,6 +8,6 @@ var value: Variant
|
||||
var type: ModifierType = ModifierType.ADDITIVE
|
||||
var internal_name: String
|
||||
var title: String
|
||||
var rarity: GlobalConst.ModRarity
|
||||
var rarity: GlobalConst.Rarity
|
||||
var tex: Texture2D
|
||||
var description: String
|
||||
|
Reference in New Issue
Block a user