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