More work on quests
This commit is contained in:
42
data/quests/ann-marie-nestor2.gd
Normal file
42
data/quests/ann-marie-nestor2.gd
Normal file
@@ -0,0 +1,42 @@
|
||||
extends Quest
|
||||
|
||||
func _init() -> void:
|
||||
name = "[1] A Quest for Nestor's Woods that Ann Marie Promised Me"
|
||||
super._init()
|
||||
|
||||
func setup() -> void:
|
||||
|
||||
var event_weights = [1,1,1,1,1,1,1,1,2,2,2,2,3,3,3,4,4,5]
|
||||
var num_events = 3 # event_weights.pick_random()
|
||||
#The first event is guaranteed to be at the 50% mark.
|
||||
var first : bool = true
|
||||
var pranges : Array = []
|
||||
var margin : float = 0.1
|
||||
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
|
||||
if first:
|
||||
#Make invisible
|
||||
evt.progress_point = .5
|
||||
pranges.append([margin, evt.progress_point-margin])
|
||||
pranges.append([evt.progress_point+margin, 1 - margin])
|
||||
first = false
|
||||
else:
|
||||
evt.hidden = true
|
||||
pranges.shuffle()
|
||||
var range = pranges.pop_back()
|
||||
evt.progress_point = randf_range(range[0], range[1])
|
||||
if evt.progress_point - range[0] >= 2 * margin:
|
||||
pranges.append([range[0], evt.progress_point - margin])
|
||||
if range[1] - evt.progress_point >= 2 * margin:
|
||||
pranges.append([evt.progress_point + margin, range[1]])
|
||||
events.append(evt)
|
||||
events.sort_custom(func(a,b): return a.progress_point < b.progress_point)
|
||||
desc = "One day Ann Marie will write me."
|
||||
location = Quest.Locations.NESTORS_WOODS
|
||||
rewards = {"exp":10, "gold":5}
|
||||
guild_rewards = {"glory":10, "gold":5}
|
||||
covenant_cost = 5
|
||||
|
||||
1
data/quests/ann-marie-nestor2.gd.uid
Normal file
1
data/quests/ann-marie-nestor2.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://w6t0lkkxmbl2
|
||||
49
data/quests/feral_pigs.gd
Normal file
49
data/quests/feral_pigs.gd
Normal file
@@ -0,0 +1,49 @@
|
||||
extends Quest
|
||||
|
||||
func _init() -> void:
|
||||
name = "Fight of the Feral Pigs"
|
||||
location = Quest.Locations.NESTORS_WOODS
|
||||
difficulty = 1
|
||||
super._init()
|
||||
|
||||
func setup() -> void:
|
||||
|
||||
var event_weights = [1,1,1,1,1,1,1,1,2,2,2,2,3,3,3]
|
||||
var num_events = event_weights.pick_random()
|
||||
#The first event is guaranteed to be at the 50% mark.
|
||||
var first : bool = true
|
||||
var pranges : Array = []
|
||||
var margin : float = 0.1
|
||||
for i in range(num_events):
|
||||
var evt : Quest.Event = Quest.Event.new()
|
||||
evt.type = Quest.Event.Type.COMBAT
|
||||
evt.enemies = []
|
||||
for j in range(randi() %3 + 1):
|
||||
evt.enemies.append("feral pig")
|
||||
evt.time = 600
|
||||
evt.completed.connect(_on_combat_complete.bind(evt))
|
||||
if first:
|
||||
#Make invisible
|
||||
evt.progress_point = .5
|
||||
pranges.append([margin, evt.progress_point-margin])
|
||||
pranges.append([evt.progress_point+margin, 1 - margin])
|
||||
first = false
|
||||
else:
|
||||
evt.hidden = true
|
||||
pranges.shuffle()
|
||||
var range = pranges.pop_back()
|
||||
evt.progress_point = randf_range(range[0], range[1])
|
||||
if evt.progress_point - range[0] >= 2 * margin:
|
||||
pranges.append([range[0], evt.progress_point - margin])
|
||||
if range[1] - evt.progress_point >= 2 * margin:
|
||||
pranges.append([evt.progress_point + margin, range[1]])
|
||||
events.append(evt)
|
||||
events.sort_custom(func(a,b): return a.progress_point < b.progress_point)
|
||||
desc = "Pigs got out of Old John’s farm again. Poor fella don’t know up from down– at his age, he can’t help it. Trouble is, pigs are causing trouble in them there woods and John sure ain’t takin’ care of it. Handle 'em for us, would you?"
|
||||
location = Quest.Locations.NESTORS_WOODS
|
||||
rewards = {"exp":10, "gold":5}
|
||||
guild_rewards = {"glory":5, "gold":5}
|
||||
covenant_cost = 5
|
||||
|
||||
func _on_combat_complete(event : Quest.Event) -> void:
|
||||
rewards.gold += 2 * len(event.enemies)
|
||||
1
data/quests/feral_pigs.gd.uid
Normal file
1
data/quests/feral_pigs.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://nnl5qvb3csr0
|
||||
@@ -1,359 +0,0 @@
|
||||
class_name Quest extends Resource
|
||||
|
||||
#The list of available quests
|
||||
static var list : Array[Quest]
|
||||
static var last_id : int = 1
|
||||
|
||||
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 hidden : bool = false
|
||||
var type : Type = Type.WAIT
|
||||
var enemies : Array[String] = []
|
||||
var progress_point : float = 0
|
||||
var time : float = 1
|
||||
var time_elapsed
|
||||
var complete : bool = false
|
||||
signal completed()
|
||||
signal failed()
|
||||
|
||||
var participants : Array = []
|
||||
var turn_queue : Array = []
|
||||
var busy_list : Array = []
|
||||
var combat_state
|
||||
var dex_speed : int
|
||||
|
||||
func setup() -> void:
|
||||
pass
|
||||
|
||||
func save() -> Dictionary:
|
||||
var d : Dictionary = {}
|
||||
d.hidden = hidden
|
||||
d.type = type
|
||||
d.enemies = enemies
|
||||
d.progress_point = progress_point
|
||||
d.time = time
|
||||
d.time_elapsed = time_elapsed
|
||||
d.complete = complete
|
||||
return d
|
||||
|
||||
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:
|
||||
enemy.flip_h()
|
||||
quest.questview.pause_setting()
|
||||
quest.questview.place_enemy(enemy, true)
|
||||
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)
|
||||
time = 1.25
|
||||
time_elapsed = 0
|
||||
var c_order : Array = []
|
||||
var dex_speed = 0
|
||||
for p in participants:
|
||||
p.show_lifebar(true)
|
||||
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 / max(1, 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 = 3
|
||||
time_elapsed = 0
|
||||
|
||||
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:
|
||||
if combatant.position != combatant.reset_position:
|
||||
combatant.position_reset()
|
||||
combatant.arrived.connect(_on_combat_action_complete.bind(requeue, combatant), CONNECT_ONE_SHOT)
|
||||
return
|
||||
|
||||
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()
|
||||
combatant.approach(target, combatant.melee_range)
|
||||
combatant.arrived.connect(execute_attack.bind(combatant, target), CONNECT_ONE_SHOT)
|
||||
|
||||
func get_enemy_list(combatant) -> Array:
|
||||
var lst = []
|
||||
for p in participants:
|
||||
if p != combatant:
|
||||
lst.append(p)
|
||||
return lst
|
||||
|
||||
func resolve_combat() -> void:
|
||||
for p in participants:
|
||||
p.show_lifebar(false)
|
||||
pass
|
||||
|
||||
func process(delta : float) -> void:
|
||||
#TODO: Make quest combat work
|
||||
match(type):
|
||||
Type.COMBAT:
|
||||
if time != 0:
|
||||
time_elapsed += delta
|
||||
if time_elapsed >= time:
|
||||
time = 0
|
||||
time_elapsed = 0
|
||||
else:
|
||||
return
|
||||
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:
|
||||
resolve_combat()
|
||||
complete = true
|
||||
completed.emit()
|
||||
Type.WAIT:
|
||||
time_elapsed += delta
|
||||
if time_elapsed >= time:
|
||||
complete = true
|
||||
completed.emit()
|
||||
|
||||
var id : int
|
||||
var base_name : String = ""
|
||||
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:
|
||||
last_id += 1
|
||||
id = last_id
|
||||
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)
|
||||
questview.show_quest_complete()
|
||||
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"
|
||||
|
||||
static func load_quest_list() -> void:
|
||||
var path = ProjectSettings.get_setting_with_override("data/quests/directory")
|
||||
var dir = DirAccess.open(path)
|
||||
var quest : Quest
|
||||
if dir:
|
||||
dir.list_dir_begin()
|
||||
var filename = dir.get_next()
|
||||
while filename != "":
|
||||
if not dir.current_is_dir() and filename.get_extension() == "gd":
|
||||
var file = load(path + "/" + filename).new()
|
||||
if file is Quest:
|
||||
list.append(file)
|
||||
filename = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
|
||||
static func generate(parameters : Dictionary) -> Quest:
|
||||
return null
|
||||
|
||||
func save() -> Dictionary:
|
||||
var d : Dictionary = {}
|
||||
d.id = id
|
||||
d.name = name
|
||||
d.base_name = base_name
|
||||
d.desc = desc
|
||||
d.difficulty = difficulty
|
||||
d.location = location
|
||||
d.steps = steps
|
||||
#TODO: Convert these!
|
||||
#d.rewards = rewards
|
||||
#d.guild_rewards = guild_rewards
|
||||
|
||||
d.covenant_cost = covenant_cost
|
||||
d.length = length
|
||||
d.progress = progress
|
||||
d.current_step = current_step
|
||||
d.taken = taken
|
||||
d.status = status
|
||||
|
||||
var lst : Array = []
|
||||
for evt in events:
|
||||
lst.append(evt.save())
|
||||
d.events = lst
|
||||
return d
|
||||
@@ -1 +0,0 @@
|
||||
uid://bowt76gfx40pv
|
||||
@@ -1,7 +1,11 @@
|
||||
extends Quest
|
||||
|
||||
func setup() -> void:
|
||||
func _init() -> void:
|
||||
name = "A Sticky Situation"
|
||||
location = Quest.Locations.VOID
|
||||
super._init()
|
||||
|
||||
func setup() -> void:
|
||||
var event_weights = [1,1,1,1,1,1,1,1,2,2,2,2,3,3,3,4,4,5]
|
||||
var num_events = 3 # event_weights.pick_random()
|
||||
#The first event is guaranteed to be at the 50% mark.
|
||||
|
||||
Reference in New Issue
Block a user