最终整理版

This commit is contained in:
2026-06-03 17:04:06 +08:00
commit 959055ce90
1240 changed files with 80570 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
extends NpcScript
#
func OnStart():
Mes("Would you like to change your hair style or color today?")
Choice("I want to try another style", OnHairstyle)
Choice("A new color", OnHaircolor)
Choice("None", OnQuit)
func OnHairstyle():
var hairstyles : Array[HairstyleData] = DB.HairstylesDB.values()
var count : int = hairstyles.size() -1
var randIdx : int = randi_range(0, count)
var newStyleIdx : int = hairstyles[randIdx]._id
if newStyleIdx == own.stat.haircolor:
randIdx = (randIdx + 1) % (count + 1)
newStyleIdx = hairstyles[randIdx]._id
own.stat.SetHairstyle(newStyleIdx)
Choice("Another style", OnHairstyle)
Choice("Perfect!", OnQuit)
func OnHaircolor():
var haircolors : Array = DB.PalettesDB[DB.Palette.HAIR].values()
var count : int = haircolors.size() - 1
var randIdx : int = randi_range(0, count)
var newColorIdx : int = haircolors[randIdx]._id
if newColorIdx == own.stat.haircolor:
randIdx = (randIdx + 1) % (count + 1)
newColorIdx = haircolors[randIdx]._id
own.stat.SetHaircolor(newColorIdx)
Choice("Another color", OnHaircolor)
Choice("Perfect!", OnQuit)
+11
View File
@@ -0,0 +1,11 @@
extends NpcScript
const animationSpeed : float = 30.0
func OnTrigger():
AddTimer(npc, animationSpeed, CloseChest)
#
func CloseChest():
if IsTriggering():
Trigger()
+6
View File
@@ -0,0 +1,6 @@
extends NpcScript
#
func OnStart():
Greeting()
AddTimer(own, 1.0, Callable())
+6
View File
@@ -0,0 +1,6 @@
extends NpcScript
#
func OnAreaEnter(player : PlayerAgent):
if player and not player.ownScript:
own.Interact(player)
+25
View File
@@ -0,0 +1,25 @@
extends NpcScript
class_name WarpGlobal
#
var warpName : String = ""
#
func OnAreaEnter(player : PlayerAgent):
if player and not player.ownScript:
if npc.spawnInfo.auto_warp:
OnWarpConfirm(player)
else:
npc.Interact(player)
func OnWarpConfirm(player : PlayerAgent):
NpcCommons.Warp(player, npc.spawnInfo.destination_map, npc.spawnInfo.destination_pos, npc.spawnInfo.direction)
func GetWarpField(_player : PlayerAgent) -> String:
return warpName
#
func OnStart():
var mapData : FileData = DB.MapsDB.get(npc.spawnInfo.destination_map, null)
if mapData:
warpName = mapData._name
+49
View File
@@ -0,0 +1,49 @@
extends NpcScript
#
const FILL_TIME : float = 10.0
const FILL_TICKS : int = 20
const FILL_TICK_TIME : float = FILL_TIME / FILL_TICKS
const MOVE_TOLERANCE_SQUARED : float = 8.0 * 8.0
var BOTTLE_ID : int = "Bottle".hash()
var WATER_BOTTLE_ID : int = "Water Bottle".hash()
#
func OnStart():
if not HasItem(BOTTLE_ID):
Mes("You would need a bottle to draw water from this well.")
elif not HasItemsSpace([[BOTTLE_ID, -1], [WATER_BOTTLE_ID, 1]]):
Mes("You don't have any available space on your inventory.")
else:
Mes("Hold still while your bottle is filling...")
Action(StartFill)
func StartFill():
OnFillTick(own.position, 0)
func OnFillTick(startPos : Vector2, tick : int):
if own.position.distance_squared_to(startPos) > MOVE_TOLERANCE_SQUARED:
ClearTracker()
Notification("You moved too far from the well!")
elif not HasItem(BOTTLE_ID):
ClearTracker()
elif tick >= FILL_TICKS:
CompleteFill()
else:
DisplayTracker("Filling...", tick, FILL_TICKS, "%")
AddTimer(own, FILL_TICK_TIME, OnFillTick.bind(startPos, tick + 1), own.nick)
func CompleteFill():
NpcCommons.ClearTracker(own)
if NpcCommons.RemoveItem(own, BOTTLE_ID):
NpcCommons.AddItem(own, WATER_BOTTLE_ID)
PromptNext()
func PromptNext():
if HasItem(BOTTLE_ID):
Mes("Your bottle is now filled with fresh water. You have another empty bottle.")
Choice("Fill another bottle", StartFill)
Choice("Leave")
else:
Mes("Your bottle is now filled with fresh water.")
@@ -0,0 +1,134 @@
extends NpcScript
#
var waitingPlayer : PlayerAgent = null
var games : Dictionary = {}
#
func JoinPvP(player : PlayerAgent) -> int:
if waitingPlayer and is_instance_valid(waitingPlayer) and waitingPlayer != player:
var game : Dictionary = _CreateGame(waitingPlayer, player)
games[waitingPlayer.get_rid().get_id()] = game
games[player.get_rid().get_id()] = game
NpcCommons.PushNotification(waitingPlayer, "An opponent has arrived! Talk to %s." % npc.nick)
waitingPlayer = null
return 1
waitingPlayer = player
return 0
func GetGame(player : PlayerAgent) -> Dictionary:
return games.get(player.get_rid().get_id(), {})
func GetHand(player : PlayerAgent) -> Array:
var game : Dictionary = GetGame(player)
if game.is_empty():
return []
if game.get("p1") == player:
return game.get("p1Hand", [])
return game.get("p2Hand", [])
func DrawCard(player : PlayerAgent) -> int:
var game : Dictionary = GetGame(player)
if game.is_empty():
return -1
var gameDeck : Array = game.get("deck", [])
if gameDeck.is_empty():
return -1
return gameDeck.pop_back()
func FinishHand(player : PlayerAgent, value : int, busted : bool):
var game : Dictionary = GetGame(player)
if game.is_empty():
return
if game.get("p1") == player:
game["p1Done"] = true
game["p1Value"] = value
game["p1Busted"] = busted
else:
game["p2Done"] = true
game["p2Value"] = value
game["p2Busted"] = busted
if game.get("p1Done", false) and game.get("p2Done", false):
_ResolveGame(game)
func LeavePvP(player : PlayerAgent):
if not player or not is_instance_valid(player):
return
if waitingPlayer == player:
waitingPlayer = null
var rid : int = player.get_rid().get_id()
if games.has(rid):
var game : Dictionary = games[rid]
if not game.has("result"):
var isP1 : bool = game.get("p1") == player
var opponent = game.get("p2") if isP1 else game.get("p1")
if isP1:
game["p1Done"] = true
game["p1Value"] = 0
game["p1Busted"] = true
else:
game["p2Done"] = true
game["p2Value"] = 0
game["p2Busted"] = true
_ResolveGame(game)
if opponent and is_instance_valid(opponent):
NpcCommons.PushNotification(opponent, "Your opponent left. You win!")
_ClearGameCallbacks(game)
games.erase(rid)
func ForfeitPvP(player : PlayerAgent):
LeavePvP(player)
func CleanupGame(player : PlayerAgent):
var game : Dictionary = GetGame(player)
if not game.is_empty():
_ClearGameCallbacks(game)
games.erase(player.get_rid().get_id())
#
func _CreateGame(p1 : PlayerAgent, p2 : PlayerAgent) -> Dictionary:
var gameDeck : Array = []
gameDeck.resize(52)
for i : int in range(52):
gameDeck[i] = i
gameDeck.shuffle()
Callback.OneShotCallback(p1.tree_exiting, _PlayerLeft.bind(p1))
Callback.OneShotCallback(p2.tree_exiting, _PlayerLeft.bind(p2))
return {
"p1": p1, "p2": p2,
"deck": gameDeck,
"p1Hand": [gameDeck.pop_back(), gameDeck.pop_back()],
"p2Hand": [gameDeck.pop_back(), gameDeck.pop_back()],
"p1Value": 0, "p2Value": 0,
"p1Done": false, "p2Done": false,
"p1Busted": false, "p2Busted": false,
}
func _ClearGameCallbacks(game : Dictionary):
var p1 = game.get("p1")
var p2 = game.get("p2")
if p1 and is_instance_valid(p1):
Callback.ClearOneShot(p1.tree_exiting)
if p2 and is_instance_valid(p2):
Callback.ClearOneShot(p2.tree_exiting)
func _ResolveGame(game : Dictionary):
var p1v : int = game.get("p1Value", 0)
var p2v : int = game.get("p2Value", 0)
var p1b : bool = game.get("p1Busted", false)
var p2b : bool = game.get("p2Busted", false)
if p1b and p2b:
game["result"] = "draw"
elif p1b:
game["result"] = "p2"
elif p2b:
game["result"] = "p1"
elif p1v > p2v:
game["result"] = "p1"
elif p2v > p1v:
game["result"] = "p2"
else:
game["result"] = "draw"
func _PlayerLeft(player : PlayerAgent):
LeavePvP(player)
@@ -0,0 +1,359 @@
extends NpcScript
class_name TicTacToeGlobal
#
static var BOARD_POSITION : Vector2i = Vector2i(120, 80) * 32 + Vector2i(16, 32) # Tile 120,80 with an offset of 16,32 pixels
static var CELL_ID : int = "TicTacToeCell".hash()
const CELL_COUNT : int = 9
const WINNING_LINES : Array[Array] = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
const IDLE_TIMEOUT : float = 30.0
const NPC_DELAY_MIN : float = 0.2
const NPC_DELAY_MAX : float = 1.0
#
enum State
{
NONE = 0,
X,
O
}
var boardStates : Array[State] = [
State.NONE, State.NONE, State.NONE,
State.NONE, State.NONE, State.NONE,
State.NONE, State.NONE, State.NONE
]
var boardNpcs : Array[NpcAgent] = [
null, null, null,
null, null, null,
null, null, null
]
var playerX : PlayerAgent = null
var playerO : PlayerAgent = null
var idleTimer : Timer = null
var startStep : State = State.NONE
var currentTurn : State = State.NONE
# Board handling
func SpawnCells():
var map : WorldMap = WorldAgent.GetMapFromAgent(npc)
if not map:
return
var inst : WorldInstance = WorldAgent.GetInstanceFromAgent(npc)
if not inst:
return
var spawn : SpawnObject = SpawnObject.new()
spawn.id = CELL_ID
spawn.type = "Npc"
spawn.state = ActorCommons.State.IDLE
spawn.is_persistant = false
spawn.map = map
for cellIdx in CELL_COUNT:
spawn.spawn_position = BOARD_POSITION + Vector2i((cellIdx % 3) * 32, int(cellIdx / 3.0) * 32)
var spawnedAgent : BaseAgent = WorldAgent.CreateAgent(spawn, inst.id)
if spawnedAgent and spawnedAgent is NpcAgent:
boardNpcs[cellIdx] = spawnedAgent
boardNpcs[cellIdx].interacted.connect(CellSelected.bind(boardNpcs[cellIdx]))
func CellSelected(playerAgent : BaseAgent, cellAgent : NpcAgent):
if playerAgent is not PlayerAgent:
return
if cellAgent:
var cellIdx : int = boardNpcs.find(cellAgent)
if cellIdx >= 0:
MakeMove(playerAgent, cellIdx)
func DespawnCells():
for cellIdx in CELL_COUNT:
if boardNpcs[cellIdx]:
if is_instance_valid(boardNpcs[cellIdx]):
WorldAgent.RemoveAgent.call_deferred(boardNpcs[cellIdx])
boardNpcs[cellIdx] = null
func DespawnUnusedCells():
for cellIdx in CELL_COUNT:
if boardStates[cellIdx] == State.NONE and boardNpcs[cellIdx] and is_instance_valid(boardNpcs[cellIdx]):
WorldAgent.RemoveAgent.call_deferred(boardNpcs[cellIdx])
boardNpcs[cellIdx] = null
# PvP matchmaking
func StartPvP(player : PlayerAgent) -> State:
if not player or not is_instance_valid(player):
return State.NONE
if startStep != State.O:
if not playerX or not is_instance_valid(playerX) or WorldAgent.GetInstanceFromAgent(playerX) != WorldAgent.GetInstanceFromAgent(player):
playerX = player
Callback.AddCallback(playerX.tree_exiting, LeaveQueue, [playerX], ConnectFlags.CONNECT_ONE_SHOT)
startStep = State.X
return startStep
elif not playerO or not is_instance_valid(playerO) or WorldAgent.GetInstanceFromAgent(playerO) != WorldAgent.GetInstanceFromAgent(player):
playerO = player
startStep = State.O
StartGame()
return startStep
return State.NONE
func LeavePvP(player : PlayerAgent):
if not player or not is_instance_valid(player):
if playerX and is_instance_valid(playerX):
NpcCommons.PushNotification(playerX, "Your opponent left. You win!")
elif playerO and is_instance_valid(playerO):
NpcCommons.PushNotification(playerO, "Your opponent left. You win!")
elif startStep == State.O and (player == playerX or player == playerO):
var opponent : PlayerAgent = playerO if player == playerX else playerX
if opponent and is_instance_valid(opponent):
NpcCommons.PushNotification(opponent, "Your opponent left. You win!")
EndGame()
func LeaveQueue(player : PlayerAgent):
if player and is_instance_valid(player) and player == playerX:
EndGame()
# Game management
func StartGame():
if currentTurn != State.NONE:
return
startStep = State.O
currentTurn = State.X
DespawnCells()
SpawnCells()
StartIdleTimer()
if playerX:
Callback.RemoveCallback(playerX.tree_exiting, LeaveQueue)
Callback.AddCallback(playerX.tree_exiting, LeavePvP, [playerX], ConnectFlags.CONNECT_ONE_SHOT)
NpcCommons.PushNotification(playerX, "Tic Tac Toe started! You play X!")
if playerO:
Callback.AddCallback(playerO.tree_exiting, LeavePvP, [playerO], ConnectFlags.CONNECT_ONE_SHOT)
NpcCommons.PushNotification(playerO, "Tic Tac Toe started! You play O!")
func StartPvE(player : PlayerAgent) -> bool:
if startStep == State.NONE:
playerX = player
playerO = null
StartGame()
return startStep == State.O
return false
# Move handling
func MakeMove(player : PlayerAgent, cellIndex : int) -> bool:
if currentTurn == State.NONE or cellIndex < 0 or cellIndex >= CELL_COUNT:
return false
var mark : State = boardStates[cellIndex]
if mark != State.NONE:
return false
if player == playerX and currentTurn == State.X:
mark = State.X
elif player == playerO and currentTurn == State.O:
mark = State.O
else:
return false
boardStates[cellIndex] = mark
UpdateCellVisual(cellIndex)
currentTurn = State.X if currentTurn == State.O else State.O
OnMovePlayed()
StartIdleTimer()
return true
func OnMovePlayed():
if startStep != State.O:
return
var result : State = CheckWin()
if result != State.NONE or CheckDraw():
AnnounceResult(result)
elif not playerO:
NotifyTurn()
var delay : float = randf_range(NPC_DELAY_MIN, NPC_DELAY_MAX)
Callback.SelfDestructTimer(npc, delay, PlayNPCMove)
else:
NotifyTurn()
func PlayNPCMove():
if currentTurn != State.O or playerO:
return
var npcMove : int = GetNPCMove()
if npcMove >= 0:
MakeMove(null, npcMove)
func NotifyTurn():
if currentTurn == State.NONE:
return
if playerO:
var activePlayer : PlayerAgent = playerX if currentTurn == State.X else playerO
var waitingPlayer : PlayerAgent = playerO if currentTurn == State.X else playerX
if activePlayer and is_instance_valid(activePlayer):
NpcCommons.PushNotification(activePlayer, "Your turn!")
if waitingPlayer and is_instance_valid(waitingPlayer):
NpcCommons.PushNotification(waitingPlayer, "Waiting for opponent...")
else:
if currentTurn == State.X:
if playerX and is_instance_valid(playerX):
NpcCommons.PushNotification(playerX, "Your turn!")
else:
if playerX and is_instance_valid(playerX):
NpcCommons.PushNotification(playerX, "Waiting for opponent...")
func UpdateCellVisual(cellIndex : int):
if cellIndex < 0 or cellIndex >= CELL_COUNT:
return
var cellNpc : NpcAgent = boardNpcs[cellIndex]
if not cellNpc or not is_instance_valid(cellNpc):
return
match boardStates[cellIndex]:
State.NONE:
cellNpc.state = ActorCommons.State.IDLE
State.X:
cellNpc.state = ActorCommons.State.TRIGGER
if cellNpc.interacted.is_connected(CellSelected):
cellNpc.interacted.disconnect(CellSelected)
State.O:
cellNpc.state = ActorCommons.State.SIT
if cellNpc.interacted.is_connected(CellSelected):
cellNpc.interacted.disconnect(CellSelected)
cellNpc.set_physics_process(true)
cellNpc.requireFullUpdate = true
# Idle timer
func StartIdleTimer():
StopIdleTimer()
idleTimer = Callback.SelfDestructTimer(npc, IDLE_TIMEOUT, IdleTimeout)
func StopIdleTimer():
if idleTimer and is_instance_valid(idleTimer) and not idleTimer.is_queued_for_deletion():
idleTimer.stop()
idleTimer.queue_free()
idleTimer = null
func IdleTimeout():
idleTimer = null
if currentTurn == State.NONE:
return
var loser : State = currentTurn
var winner : State = State.X if loser == State.O else State.O
var loserPlayer : PlayerAgent = playerX if loser == State.X else playerO
if loserPlayer and is_instance_valid(loserPlayer):
NpcCommons.PushNotification(loserPlayer, "You took too long! You lose.")
AnnounceResult(winner)
# Game end
func EndGame():
StopIdleTimer()
ClearPlayerCallbacks()
DespawnUnusedCells()
ResetBoard()
func ClearPlayerCallbacks():
if playerX and is_instance_valid(playerX):
Callback.RemoveCallback(playerX.tree_exiting, LeavePvP)
if playerO and is_instance_valid(playerO):
Callback.RemoveCallback(playerO.tree_exiting, LeavePvP)
func ResetBoard():
playerX = null
playerO = null
startStep = State.NONE
currentTurn = State.NONE
for cellIdx in CELL_COUNT:
boardStates[cellIdx] = State.NONE
# Win detection
func CheckWin() -> State:
for line in WINNING_LINES:
var state : State = boardStates[line[0]]
if state != State.NONE and state == boardStates[line[1]] and state == boardStates[line[2]]:
return state
return State.NONE
func CheckDraw() -> bool:
for cell in boardStates:
if cell == State.NONE:
return false
return true
func AnnounceResult(result : State):
var msg : String = ""
match result:
State.NONE:
msg = "It's a draw!"
State.X:
msg = "%s wins!" % (playerX.nick if playerX else "X")
State.O:
msg = "%s wins!" % (playerO.nick if playerO else npc.nick)
if playerX and is_instance_valid(playerX):
NpcCommons.PushNotification(playerX, msg)
if playerO and is_instance_valid(playerO):
NpcCommons.PushNotification(playerO, msg)
EndGame()
# NPC AI
func GetNPCMove() -> int:
var winMove : int = FindWinningMove(State.O)
if winMove >= 0:
return winMove
var blockMove : int = FindWinningMove(State.X)
if blockMove >= 0:
return blockMove
if boardStates[4] == State.NONE:
return 4
var corners : Array[int] = [0, 2, 6, 8]
corners.shuffle()
for corner in corners:
if boardStates[corner] == State.NONE:
return corner
var edges : Array[int] = [1, 3, 5, 7]
edges.shuffle()
for edge in edges:
if boardStates[edge] == State.NONE:
return edge
return -1
func FindWinningMove(state : State) -> int:
for line : Array in WINNING_LINES:
var totalPointInLine : int = 0
var firstEmptyIdx : int = -1
for idx : int in line:
if boardStates[idx] == state:
totalPointInLine += 1
elif boardStates[idx] == State.NONE:
firstEmptyIdx = idx
# Winning move found if 2 out of 3 point in a line are already matching the given state and if an empty index is available
if totalPointInLine == 2 and firstEmptyIdx >= 0:
return firstEmptyIdx
return -1