Extensive work on VFX for the guild, assets for the world, and portrait variance. Work on quests. Extra work on User Flow completion and file saving.

This commit is contained in:
2025-09-04 07:46:55 -04:00
parent 149ee993dc
commit 48e335f56a
134 changed files with 2232 additions and 288 deletions

View File

@@ -1,5 +1,7 @@
class_name Accessory extends Equipment
func can_equip_slot(slot : Slots) -> bool:
return slot == Slots.ACCESSORY
func item_type_name() -> String:
return "Accessory"

View File

@@ -1,5 +1,9 @@
class_name Armor extends Equipment
func can_equip_slot(slot : Slots) -> bool:
return slot == Slots.ARMOR
func item_type_name() -> String:
return "Armor"
#TODO: Add different armor classes

View File

@@ -1,4 +1,6 @@
class_name Equipment extends Item
@export var stats : StatBlock = StatBlock.new(0)
func item_type_name() -> String:
return "Equipment"

View File

@@ -1,5 +1,10 @@
class_name Item extends Resource
enum Slots{
WEAPON,
ARMOR,
ACCESSORY
}
@export var image : Texture2D
@export var name : StringName
@@ -10,5 +15,16 @@ class_name Item extends Resource
@export var per : bool
@export var grade : String = "F"
func item_type_name() -> String:
return "Item"
func can_equip_slot(slot : Slots) -> bool:
return false
static func slot_name(slot : Slots) -> String:
match(slot):
Slots.WEAPON: return "Weapon"
Slots.ARMOR: return "Armor"
Slots.ACCESSORY: return "Accessory"
return "ERROR"

View File

@@ -1,13 +1,23 @@
[gd_resource type="Resource" script_class="Weapon" load_steps=3 format=3 uid="uid://8k1lnfoi4xww"]
[gd_resource type="Resource" script_class="Weapon" load_steps=5 format=3 uid="uid://8k1lnfoi4xww"]
[ext_resource type="Texture2D" uid="uid://clrvwaqb61lpv" path="res://graphics/items/pitchfork.png" id="1_fpnr6"]
[ext_resource type="Script" uid="uid://bgn8ipx38g28o" path="res://data/items/weapon.gd" id="1_qoils"]
[ext_resource type="Script" uid="uid://727tgvtmq4nb" path="res://data/statblock.gd" id="3_hkspc"]
[sub_resource type="Resource" id="Resource_hkspc"]
script = ExtResource("3_hkspc")
STR = 3
DEX = -3
CHA = 2
PATK = 1
metadata/_custom_type_script = "uid://727tgvtmq4nb"
[resource]
script = ExtResource("1_qoils")
min_damage = 1
max_damage = 2
type = 2
stats = SubResource("Resource_hkspc")
image = ExtResource("1_fpnr6")
brief = "A humble weapon for a humble beginning."
metadata/_custom_type_script = "uid://bgn8ipx38g28o"

View File

@@ -1,6 +1,6 @@
class_name Weapon extends Equipment
enum Type{
enum Types{
FIST,
SWORD,
SPEAR,
@@ -12,21 +12,24 @@ enum Type{
}
@export var min_damage : int
@export var max_damage : int
@export var type : Type
@export var type : Types
func item_type_name() -> String:
return "Weapon (%s)" % weapon_type_name()
func can_equip_slot(slot : Slots) -> bool:
return slot == Slots.WEAPON
func primary_stat() -> String:
return "Deals %d-%d base damage." % [min_damage, max_damage]
func weapon_type_name() -> String:
match(type):
Type.FIST: return "Fist"
Type.SWORD: return "Sword"
Type.SPEAR: return "Spear"
Type.STAFF: return ""
Type.DAGGER: return ""
Type.HAMMER: return ""
Type.WHIP: return ""
Types.FIST: return "Fist"
Types.SWORD: return "Sword"
Types.SPEAR: return "Spear"
Types.STAFF: return "Staff"
Types.DAGGER: return "Dagger"
Types.HAMMER: return "Hammer"
Types.WHIP: return "Whip"
return "Unknown"

View File

@@ -1,5 +1,6 @@
[gd_resource type="Resource" script_class="JobData" load_steps=2 format=3 uid="uid://db4xces0v3het"]
[gd_resource type="Resource" script_class="JobData" load_steps=3 format=3 uid="uid://db4xces0v3het"]
[ext_resource type="PackedScene" uid="uid://doxmdg4dpnjp1" path="res://graphics/portraits/farmer.tscn" id="1_0iatm"]
[ext_resource type="Script" uid="uid://byr5ai03cpa5s" path="res://data/jobs/job_data.gd" id="1_clwor"]
[resource]
@@ -12,4 +13,6 @@ max_INT = 3
max_CHA = 2
max_FAI = 2
max_LUK = 2
portrait = ExtResource("1_0iatm")
equippable_weapons = ["Spear", "Fist", "Hammer"]
metadata/_custom_type_script = "uid://byr5ai03cpa5s"

View File

@@ -0,0 +1,21 @@
[gd_resource type="Resource" script_class="JobData" load_steps=2 format=3 uid="uid://bcbnt88ss6loi"]
[ext_resource type="Script" uid="uid://byr5ai03cpa5s" path="res://data/jobs/job_data.gd" id="1_2ar68"]
[resource]
script = ExtResource("1_2ar68")
name = "Guildleader"
type = 1
min_STR = null
max_STR = null
min_DEX = null
max_DEX = null
min_INT = null
max_INT = null
min_CHA = null
max_CHA = null
min_FAI = null
max_FAI = null
min_LUK = null
max_LUK = null
metadata/_custom_type_script = "uid://byr5ai03cpa5s"

View File

@@ -26,7 +26,11 @@ var test
@export var portrait : PackedScene
@export var equippable_weapons : Array[String] = []
#TODO: Implement a more interesting tnl for different jobs
func get_tnl(lvl : int) -> int:
return lvl * 10
func can_equip(item):
return true

270
data/quests/quest.gd Normal file
View File

@@ -0,0 +1,270 @@
class_name Quest extends Resource
enum Status{
OPEN,
TAKEN,
IN_PROGRESS,
COMPLETED,
FAILED,
CLOSED
}
enum Locations{
NESTORS_WOODS
}
class Event:
var enemy_types: Dictionary[String, PackedScene] = {
"goo": preload("res://templates/enemies/goo.tscn")
}
enum Type{
WAIT,
COMBAT,
CHOICE
}
enum CombatState{
FIGHTING,
VICTORY,
DEFEAT
}
var type : Type = Type.WAIT
var enemies : Array[String] = []
var time : float = 1
var time_elapsed
signal completed()
signal failed()
var participants : Array = []
var turn_queue : Array = []
var busy_list : Array = []
var combat_state
var dex_speed : int
func start(quest : Quest) -> void:
match(type):
Type.WAIT:
return
Type.COMBAT:
combat_state = CombatState.FIGHTING
var enemy_list = []
for enemy_name in enemies:
enemy_list.append(enemy_types[enemy_name].instantiate())
quest.questview.set_questor_animation("idle")
for enemy in enemy_list:
quest.questview.pause_setting()
quest.questview.place_enemy(enemy)
quest.questview.set_enemy_animation(enemy, "idle")
start_combat([quest.questor.quest_sprite], enemy_list)
func start_combat(adventurers : Array, enemies : Array) -> void:
participants = []
participants.append_array(adventurers)
participants.append_array(enemies)
var c_order : Array = []
var dex_speed = 0
for p in participants:
c_order.append([p, p.stats.DEX])
if p.stats.DEX > dex_speed:
dex_speed = p.stats.DEX
c_order.sort_custom(func(a,b): return a[1] > b[1])
var delay = 5
var last_time = 0
for c in c_order:
c[0].busy.connect(_on_busy.bind(c[0]))
c[0].action_complete.connect(_on_combat_action_complete.bind(c[0]))
c[0].died.connect(_on_death.bind(c[0]))
var time = delay * dex_speed
if c[1] != 0:
time = delay * dex_speed / c[1]
turn_queue.append({"combatant":c[0], "time": time - last_time})
last_time = time
func execute_attack(combatant : QuestSprite, target : QuestSprite) -> void:
var tween = combatant.create_tween()
tween.tween_interval(.25)
tween.tween_callback(combatant.attack.bind(target))
func add_to_turn_queue(combatant) -> void:
#Calculate time
var time = dex_speed / combatant.stats.DEX
#Walk through list to find insertion point
var idx = -1
for i in range(len(turn_queue)):
if turn_queue[i].time > time:
idx = i
break
else:
time -= turn_queue[i].time
var entry = {"combatant":combatant, "time":time}
if idx == -1:
turn_queue.append(entry)
else:
turn_queue[idx].time -= time
turn_queue.insert(idx,entry)
func _on_busy(combatant : QuestSprite) -> void:
busy_list.append(combatant)
func _on_death(killer : QuestSprite, combatant : QuestSprite) -> void:
busy_list.erase(combatant)
remove_from_queue(combatant)
participants.erase(combatant)
if killer != combatant:
var xp = Game.calculate_kill_exp(killer, combatant)
print("%s has earned %d exp" % [killer.name, xp])
killer.exp += xp
var enemy_list : Array = get_enemy_list(killer)
if len(enemy_list) == 0:
if killer is QuestorSprite:
victory()
else:
defeat()
func victory():
print("Questor won!")
combat_state = CombatState.VICTORY
for p : QuestorSprite in participants:
p.check_levelup()
#TODO: Notify player if level up occurs
time = 5
func defeat():
print("Questor lost!")
combat_state = CombatState.DEFEAT
failed.emit()
func remove_from_queue(combatant : QuestSprite) -> void:
var idx = -1
for i in range(len(turn_queue)):
if turn_queue[i].combatant == combatant:
idx = i
break
if idx != -1:
turn_queue.remove_at(idx)
else:
printerr("Tried to remove someone not in the turn queue")
func _on_combat_action_complete(requeue : bool, combatant : QuestSprite) -> void:
busy_list.erase(combatant)
if requeue:
add_to_turn_queue(combatant)
func execute_action(combatant) -> void:
busy_list = [combatant]
#TODO: Come up with other options than just swinging at each other
var enemies : Array = get_enemy_list(combatant)
var target = enemies.pick_random()
execute_attack(combatant, target)
func get_enemy_list(combatant) -> Array:
var lst = []
for p in participants:
if p != combatant:
lst.append(p)
return lst
func resolve_combat() -> void:
pass
func process(delta : float) -> void:
#TODO: Make quest combat work
match(type):
Type.COMBAT:
match(combat_state):
CombatState.FIGHTING:
if len(busy_list) < 1:
if len(turn_queue) > 0:
turn_queue[0].time -= delta
if turn_queue[0].time <= 0:
var c = turn_queue.pop_front()
print("%s taking a turn!" % [c.combatant.name])
if len(turn_queue) > 0:
turn_queue[0].time += c.time
execute_action(c.combatant)
else:
resolve_combat()
CombatState.VICTORY:
time_elapsed += delta
if time_elapsed >= time:
completed.emit()
Type.WAIT:
time_elapsed += delta
if time_elapsed >= time:
completed.emit()
var name : String = "A Basic Quest"
var desc : String = "The default quest, with no special anything."
var difficulty : int = 1
var location : Locations
var steps : int = 1
var rewards : Dictionary
var guild_rewards : Dictionary
var covenant_cost : int = 1
var length : float = 10
var events : Array[Event] = []
var progress : float = 0
var current_step : int = 0
var taken : bool = false
var status : Status = Status.OPEN
var questview : QuestView = null
var questor : Adventurer = null
signal status_changed(status : Status)
func _init() -> void:
pass
func initiate(member : Adventurer) -> void:
questor = member
status = Status.TAKEN
status_changed.emit(Status.TAKEN)
func fail() -> void:
status = Status.FAILED
status_changed.emit(Status.FAILED)
func complete() -> void:
status = Status.COMPLETED
status_changed.emit(Status.COMPLETED)
for reward in rewards.keys():
if reward == "gold":
questor.gain_gold(rewards[reward])
elif reward == "exp":
questor.gain_exp(rewards[reward])
#TODO: Implement other reward types
#elif rewards[reward] is Item:
# questor.gain_item()
#else it's a guild item they'll bring back for us
Game.notice("%s completed the quest '%s'!" % [questor.full_name(), name])
func num_events() -> int:
return len(events)
#TODO: Put in quest requirements
func is_eligible(member : Adventurer) -> bool:
return !taken
func is_taken() -> bool:
return status == Status.TAKEN
func location_name() -> String:
match(location):
Locations.NESTORS_WOODS: return "Nestor's Woods"
return "ERROR"
func difficulty_name() -> String:
match(difficulty):
0: return "None"
1: return "Trivial"
2: return "Moderate"
3: return "Severe"
4: return "Extreme"
5: return "Legendary"
_: return "Unknown"

1
data/quests/quest.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bowt76gfx40pv

View File

@@ -0,0 +1,18 @@
extends Quest
func _init() -> void:
name = "A Sticky Situation"
var event_weights = [1,1,1,1,1,1,1,1,2,2,2,2,3,3,3,4,4,5]
var num_events = event_weights.pick_random()
for i in range(num_events):
var evt : Quest.Event = Quest.Event.new()
evt.type = Quest.Event.Type.COMBAT
evt.enemies = ["goo"]
evt.time = 5
events.append(evt)
desc = "Nestors Woods is facing a slime invasion and the farmers are getting nervous, send an adventurer to help squash that sticky situation!"
location =
rewards = {"exp":10, "gold":5}
guild_rewards = {"glory":10, "gold":5}
covenant_cost = 5

View File

@@ -0,0 +1 @@
uid://bvm35omddo1fu

View File

@@ -1,13 +1,42 @@
class_name StatBlock extends Resource
@export var STR : int = 1
@export var DEX : int = 1
@export var INT : int = 1
@export var CHA : int = 1
@export var FAI : int = 1
@export var LUK : int = 1
@export var STR : int = 0
@export var DEX : int = 0
@export var INT : int = 0
@export var CHA : int = 0
@export var FAI : int = 0
@export var LUK : int = 0
@export var PATK : int = 0
@export var PDEF : int = 0
@export var MATK : int = 0
@export var MDEF : int = 0
func _init(start : int = 0) -> void:
STR = start
DEX = start
INT = start
CHA = start
FAI = start
LUK = start
PATK = start
PDEF = start
MATK = start
MDEF = start
func _to_string() -> String:
var string = "%s {" % [resource_scene_unique_id]
string += str(STR) + ", "
string += str(DEX) + ", "
string += str(INT) + ", "
string += str(CHA) + ", "
string += str(FAI) + ", "
string += str(LUK) + ", "
string += str(PATK) + ", "
string += str(PDEF) + ", "
string += str(MATK) + ", "
string += str(MDEF) + "}"
return string
static func copy(block : StatBlock) -> StatBlock:
var b = StatBlock.new()
b.STR = block.STR
@@ -16,4 +45,8 @@ static func copy(block : StatBlock) -> StatBlock:
b.CHA = block.CHA
b.FAI = block.FAI
b.LUK = block.LUK
b.PATK = block.PATK
b.PDEF = block.PDEF
b.MATK = block.MATK
b.MDEF = block.MDEF
return b