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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
class_name Equipment extends Item
|
||||
|
||||
@export var stats : StatBlock = StatBlock.new(0)
|
||||
|
||||
func item_type_name() -> String:
|
||||
return "Equipment"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
21
data/jobs/guildleader.tres
Normal file
21
data/jobs/guildleader.tres
Normal 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"
|
||||
@@ -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
270
data/quests/quest.gd
Normal 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
1
data/quests/quest.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bowt76gfx40pv
|
||||
18
data/quests/sticky_situation.gd
Normal file
18
data/quests/sticky_situation.gd
Normal 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 = "Nestor’s 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
|
||||
|
||||
1
data/quests/sticky_situation.gd.uid
Normal file
1
data/quests/sticky_situation.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bvm35omddo1fu
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user