Skip to content

feat(npc): restore missing dialogue keywords for the remaining Thais NPCs (part 3/3) #2266

feat(npc): restore missing dialogue keywords for the remaining Thais NPCs (part 3/3)

feat(npc): restore missing dialogue keywords for the remaining Thais NPCs (part 3/3) #2266

Workflow file for this run

---
name: Deploy to Neep Host
on:
workflow_dispatch:
issue_comment:
types: [created]
concurrency:
group: deploy-neep
cancel-in-progress: false
jobs:
deploy:
if: |
(
github.event_name == 'workflow_dispatch'
) ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/deploy') &&
(
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'
)
)
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
steps:
# ---------------------------------------------------------
# Authenticate gh
# ---------------------------------------------------------
- name: Authenticate gh
run: echo "${{ github.token }}" | gh auth login --with-token
# ---------------------------------------------------------
# React to command: 👀
# ---------------------------------------------------------
- name: React to comment (👀)
if: github.event_name == 'issue_comment'
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
with:
token: ${{ github.token }}
comment-id: ${{ github.event.comment.id }}
reactions: eyes
# ---------------------------------------------------------
# Detect PR when running manually
# ---------------------------------------------------------
- name: Detect open PR for branch
id: detect_pr
if: github.event_name == 'workflow_dispatch'
run: |
BRANCH="${{ github.ref_name }}"
PR_NUMBER="$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number' || true)"
{
if [ -n "$PR_NUMBER" ]; then
echo "pr_number=$PR_NUMBER"
echo "has_pr=true"
else
echo "has_pr=false"
fi
} >> "$GITHUB_OUTPUT"
# ---------------------------------------------------------
# Extract PR branch from comment
# ---------------------------------------------------------
- uses: xt0rted/pull-request-comment-branch@e8b8daa837e8ea7331c0003c9c316a64c6d8b0b1
if: github.event_name == 'issue_comment'
id: comment-branch
# ---------------------------------------------------------
# Setup configs (branch, actor, PR number)
# ---------------------------------------------------------
- name: Setup configurations
id: setup
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
if [ "${{ github.event_name }}" = "issue_comment" ]; then
{
echo "name=${{ steps.comment-branch.outputs.head_ref }}"
echo "actor=${{ github.event.comment.user.login }}"
echo "pr_number=${{ github.event.issue.number }}"
echo "has_pr=true"
if printf '%s\n' "$COMMENT_BODY" | grep -Fqi -- '/deploy clean'; then
echo "is_clean=true"
else
echo "is_clean=false"
fi
} >> "$GITHUB_OUTPUT"
else
{
echo "name=${{ github.ref_name }}"
echo "actor=${{ github.actor }}"
echo "pr_number=${{ steps.detect_pr.outputs.pr_number }}"
echo "has_pr=${{ steps.detect_pr.outputs.has_pr }}"
echo "is_clean=false"
} >> "$GITHUB_OUTPUT"
fi
# ---------------------------------------------------------
# Remote: checkout, build, deploy
# ---------------------------------------------------------
- name: Remote build and deploy
id: remote
uses: appleboy/ssh-action@91f3272fc5907f4699dcf59761eb622a07342f5a
with:
host: ${{ secrets.NEEP_HOST }}
username: root
password: ${{ secrets.NEEP_PASS }}
timeout: 300s
command_timeout: 30m
script: |
set -euo pipefail
: > /tmp/canary-deploy.log
exec > >(tee /tmp/canary-deploy.log) 2>&1
CANARY_DIR=/opt/canary
VCPKG_DIR="$HOME/vcpkg"
BUILD_PRESET=linux-release
BUILD_DIR="$CANARY_DIR/build/$BUILD_PRESET"
BRANCH="${{ steps.setup.outputs.name }}"
IS_CLEAN="${{ steps.setup.outputs.is_clean }}"
HAS_PR="${{ steps.setup.outputs.has_pr }}"
PR_NUMBER="${{ steps.setup.outputs.pr_number }}"
export VCPKG_ROOT="$VCPKG_DIR"
export PATH="$VCPKG_DIR:$PATH"
echo "=== [1/7] Checking out branch: $BRANCH ==="
cd "$CANARY_DIR"
git fetch origin
if [ "$HAS_PR" = "true" ] && [ -n "$PR_NUMBER" ]; then
# PR head may live in a fork; use the GitHub PR ref so this works
# for both same-repo and fork PRs.
git fetch origin "+refs/pull/${PR_NUMBER}/head:refs/remotes/pr/${PR_NUMBER}"
git checkout -B "$BRANCH" "refs/remotes/pr/${PR_NUMBER}"
git reset --hard "refs/remotes/pr/${PR_NUMBER}"
else
git checkout "$BRANCH" 2>/dev/null || git checkout -b "$BRANCH"
git reset --hard "origin/$BRANCH"
fi
git clean -fdx --exclude=build --exclude=config.lua
echo "=== [2/7] Updating vcpkg ==="
if [ ! -d "$VCPKG_DIR/.git" ]; then
echo "ERROR: $VCPKG_DIR is not a git clone of vcpkg"
exit 1
fi
git -C "$VCPKG_DIR" fetch --tags origin
git -C "$VCPKG_DIR" reset --hard origin/master
"$VCPKG_DIR/bootstrap-vcpkg.sh" -disableMetrics
echo "=== [3/7] Preparing build ==="
if [ "$IS_CLEAN" = "true" ]; then
echo "'/deploy clean' command detected. Removing $BUILD_DIR..."
rm -rf "$BUILD_DIR"
else
echo "Reusing $BUILD_DIR if it exists..."
fi
echo "=== [4/7] Configuring CMake ==="
cmake --preset "$BUILD_PRESET" -DTOGGLE_BIN_FOLDER=ON
echo "=== [5/7] Compiling ==="
cmake --build --preset "$BUILD_PRESET"
echo "=== [6/7] Deploying binary ==="
stopped_canary=false
restore_canary() {
if [ "$stopped_canary" = "true" ]; then
systemctl start canary || true
fi
}
trap restore_canary EXIT
if systemctl is-active --quiet canary; then
systemctl stop canary
stopped_canary=true
fi
sleep 3
if systemctl is-active --quiet canary; then
echo "ERROR: canary service is still running after stop!"
exit 1
fi
install -m 0755 "$BUILD_DIR/bin/canary" "$CANARY_DIR/canary"
ESCAPED_BRANCH="$(printf '%s\n' "$BRANCH" | sed 's/[\/&]/\\&/g')"
sed -i "s/^serverMotd = \".*\"/serverMotd = \"Welcome to the Canary Test Server! Currently running branch: $ESCAPED_BRANCH\"/" "$CANARY_DIR/config.lua"
echo "=== [7/7] Updating configs and starting service ==="
echo "Updating $CANARY_DIR/data/stages.lua"
cat << 'EOF' > "$CANARY_DIR/data/stages.lua"
-- Minlevel and multiplier are MANDATORY
-- Maxlevel is OPTIONAL, but is considered infinite by default
-- Create a stage with minlevel 1 and no maxlevel to disable stages
experienceStages = {
{
minlevel = 1,
maxlevel = 8,
multiplier = 50
},
{
minlevel = 8,
maxlevel = 100,
multiplier = 300
},
{
minlevel = 101,
maxlevel = 200,
multiplier = 250
},
{
minlevel = 201,
maxlevel = 300,
multiplier = 200
},
{
minlevel = 301,
maxlevel = 400,
multiplier = 150
},
{
minlevel = 401,
maxlevel = 500,
multiplier = 100
},
{
minlevel = 501,
maxlevel = 600,
multiplier = 80
},
{
minlevel = 601,
maxlevel = 700,
multiplier = 60
},
{
minlevel = 701,
maxlevel = 800,
multiplier = 40
},
{
minlevel = 801,
maxlevel = 900,
multiplier = 20
},
{
minlevel = 901,
maxlevel = 1000,
multiplier = 15
},
{
minlevel = 1001,
multiplier = 10
},
}
skillsStages = {
{
minlevel = 10,
maxlevel = 50,
multiplier = 50
},
{
minlevel = 51,
maxlevel = 80,
multiplier = 40
},
{
minlevel = 81,
maxlevel = 100,
multiplier = 20
},
{
minlevel = 101,
maxlevel = 110,
multiplier = 15
},
{
minlevel = 111,
multiplier = 10
},
}
magicLevelStages = {
{
minlevel = 0,
maxlevel = 60,
multiplier = 30
},
{
minlevel = 61,
maxlevel = 80,
multiplier = 25
},
{
minlevel = 81,
maxlevel = 100,
multiplier = 20
},
{
minlevel = 101,
maxlevel = 110,
multiplier = 15
},
{
minlevel = 111,
maxlevel = 120,
multiplier = 10
},
{
minlevel = 121,
multiplier = 2
},
}
EOF
echo "Updating $CANARY_DIR/data/XML/vocations.xml"
cat << 'EOF' > "$CANARY_DIR/data/XML/vocations.xml"
<?xml version="1.0" encoding="UTF-8"?>
<vocations>
<vocation id="0" clientid="0" baseid="0" name="None" description="none" magicshield="0" gaincap="10" gainhp="5" gainmana="5" gainhpticks="4000" gainhpamount="5" gainmanaticks="4000" gainmanaamount="5" manamultiplier="4.0" attackspeed="2000" basespeed="110" soulmax="100" gainsoulticks="120000" fromvoc="0">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.3" primaryShield="2.05" secondaryShield="1.25" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.5" />
<skill id="1" multiplier="2.0" />
<skill id="2" multiplier="2.0" />
<skill id="3" multiplier="2.0" />
<skill id="4" multiplier="2.0" />
<skill id="5" multiplier="1.5" />
<skill id="6" multiplier="1.1" />
</vocation>
<vocation id="1" clientid="3" baseid="1" name="Sorcerer" description="a sorcerer" magicshield="1" gaincap="10" gainhp="5" gainmana="30" gainhpticks="4000" gainhpamount="5" gainmanaticks="4000" gainmanaamount="10" manamultiplier="1.1" attackspeed="2000" basespeed="110" soulmax="100" gainsoulticks="120000" fromvoc="1" avatarlooktype="1595">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.26" primaryShield="2.0" secondaryShield="1.2" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.5" />
<skill id="1" multiplier="2.0" />
<skill id="2" multiplier="2.0" />
<skill id="3" multiplier="2.0" />
<skill id="4" multiplier="2.0" />
<skill id="5" multiplier="1.5" />
<skill id="6" multiplier="1.1" />
</vocation>
<vocation id="2" clientid="4" baseid="2" name="Druid" description="a druid" magicshield="1" gaincap="10" gainhp="5" gainmana="30" gainhpticks="4000" gainhpamount="5" gainmanaticks="4000" gainmanaamount="10" manamultiplier="1.1" attackspeed="2000" basespeed="110" soulmax="100" gainsoulticks="120000" fromvoc="2" avatarlooktype="1596">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.26" primaryShield="2.0" secondaryShield="1.2" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.5" />
<skill id="1" multiplier="1.8" />
<skill id="2" multiplier="1.8" />
<skill id="3" multiplier="1.8" />
<skill id="4" multiplier="1.8" />
<skill id="5" multiplier="1.5" />
<skill id="6" multiplier="1.1" />
</vocation>
<vocation id="3" clientid="2" baseid="3" name="Paladin" description="a paladin" magicshield="0" gaincap="20" gainhp="10" gainmana="15" gainhpticks="4000" gainhpamount="8" gainmanaticks="4000" gainmanaamount="8" manamultiplier="1.4" attackspeed="2000" basespeed="110" soulmax="100" gainsoulticks="120000" fromvoc="3" avatarlooktype="1594">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.28" primaryShield="2.08" secondaryShield="1.2" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.2" />
<skill id="1" multiplier="1.2" />
<skill id="2" multiplier="1.2" />
<skill id="3" multiplier="1.2" />
<skill id="4" multiplier="1.1" />
<skill id="5" multiplier="1.1" />
<skill id="6" multiplier="1.1" />
</vocation>
<vocation id="4" clientid="1" baseid="4" name="Knight" description="a knight" magicshield="0" gaincap="25" gainhp="15" gainmana="5" gainhpticks="4000" gainhpamount="10" gainmanaticks="4000" gainmanaamount="5" manamultiplier="3.0" attackspeed="2000" basespeed="110" soulmax="100" gainsoulticks="120000" fromvoc="4" avatarlooktype="1593">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.3" primaryShield="2.05" secondaryShield="1.25" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.1" />
<skill id="1" multiplier="1.1" />
<skill id="2" multiplier="1.1" />
<skill id="3" multiplier="1.1" />
<skill id="4" multiplier="1.4" />
<skill id="5" multiplier="1.1" />
<skill id="6" multiplier="1.1" />
</vocation>
<vocation id="5" clientid="13" baseid="1" name="Master Sorcerer" description="a master sorcerer" magicshield="1" gaincap="10" gainhp="5" gainmana="30" gainhpticks="3000" gainhpamount="15" gainmanaticks="2000" gainmanaamount="25" manamultiplier="1.1" attackspeed="2000" basespeed="110" soulmax="200" gainsoulticks="15000" fromvoc="1" avatarlooktype="1595">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.26" primaryShield="2.0" secondaryShield="1.2" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.5" />
<skill id="1" multiplier="2.0" />
<skill id="2" multiplier="2.0" />
<skill id="3" multiplier="2.0" />
<skill id="4" multiplier="2.0" />
<skill id="5" multiplier="1.5" />
<skill id="6" multiplier="1.1" />
<gem quality="0" name="lesser sage gem" />
<gem quality="1" name="sage gem" />
<gem quality="2" name="greater sage gem" />
</vocation>
<vocation id="6" clientid="14" baseid="2" name="Elder Druid" description="an elder druid" magicshield="1" gaincap="10" gainhp="5" gainmana="30" gainhpticks="3000" gainhpamount="15" gainmanaticks="2000" gainmanaamount="25" manamultiplier="1.1" attackspeed="2000" basespeed="110" soulmax="200" gainsoulticks="15000" fromvoc="2" avatarlooktype="1596">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.26" primaryShield="2.0" secondaryShield="1.2" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.5" />
<skill id="1" multiplier="1.8" />
<skill id="2" multiplier="1.8" />
<skill id="3" multiplier="1.8" />
<skill id="4" multiplier="1.8" />
<skill id="5" multiplier="1.5" />
<skill id="6" multiplier="1.1" />
<gem quality="0" name="lesser mystic gem" />
<gem quality="1" name="mystic gem" />
<gem quality="2" name="greater mystic gem" />
</vocation>
<vocation id="7" clientid="12" baseid="3" name="Royal Paladin" description="a royal paladin" magicshield="0" gaincap="20" gainhp="10" gainmana="15" gainhpticks="2000" gainhpamount="20" gainmanaticks="3000" gainmanaamount="15" manamultiplier="1.4" attackspeed="2000" basespeed="110" soulmax="200" gainsoulticks="15000" fromvoc="3" avatarlooktype="1594">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.28" primaryShield="2.08" secondaryShield="1.2" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.2" />
<skill id="1" multiplier="1.2" />
<skill id="2" multiplier="1.2" />
<skill id="3" multiplier="1.2" />
<skill id="4" multiplier="1.1" />
<skill id="5" multiplier="1.1" />
<skill id="6" multiplier="1.1" />
<gem quality="0" name="lesser marksman gem" />
<gem quality="1" name="marksman gem" />
<gem quality="2" name="greater marksman gem" />
</vocation>
<vocation id="8" clientid="11" baseid="4" name="Elite Knight" description="an elite knight" magicshield="0" gaincap="25" gainhp="15" gainmana="5" gainhpticks="2000" gainhpamount="25" gainmanaticks="3000" gainmanaamount="10" manamultiplier="3.0" attackspeed="2000" basespeed="110" soulmax="200" gainsoulticks="15000" fromvoc="4" avatarlooktype="1593">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.3" primaryShield="2.05" secondaryShield="1.25" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.4" />
<skill id="1" multiplier="1.1" />
<skill id="2" multiplier="1.1" />
<skill id="3" multiplier="1.1" />
<skill id="4" multiplier="1.1" />
<skill id="5" multiplier="1.1" />
<skill id="6" multiplier="1.1" />
<gem quality="0" name="lesser guardian gem" />
<gem quality="1" name="guardian gem" />
<gem quality="2" name="greater guardian gem" />
</vocation>
<vocation id="9" clientid="5" baseid="9" name="Monk" description="a monk" magicshield="0" gaincap="25" gainhp="10" gainmana="10" gainhpticks="4000" gainhpamount="8" gainmanaticks="4000" gainmanaamount="8" manamultiplier="1.3" attackspeed="2000" basespeed="110" soulmax="100" gainsoulticks="120000" fromvoc="9" avatarlooktype="1831">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.28" primaryShield="2.08" secondaryShield="1.2" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.1" /> <!-- fist -->
<skill id="1" multiplier="1.5" />
<skill id="2" multiplier="1.5" />
<skill id="3" multiplier="1.5" />
<skill id="4" multiplier="2.0" />
<skill id="5" multiplier="1.2" />
<skill id="6" multiplier="1.1" />
</vocation>
<vocation id="10" clientid="15" baseid="9" name="Exalted Monk" description="an exalted monk" magicshield="0" gaincap="25" gainhp="10" gainmana="10" gainhpticks="2000" gainhpamount="15" gainmanaticks="3000" gainmanaamount="20" manamultiplier="1.3" attackspeed="2000" basespeed="110" soulmax="200" gainsoulticks="15000" fromvoc="9" avatarlooktype="1831">
<formula meleeDamage="1.0" distDamage="1.0" defense="1.0" armor="1.0" />
<mitigation multiplier="1.28" primaryShield="2.08" secondaryShield="1.2" />
<pvp damageReceivedMultiplier="1.0" damageDealtMultiplier ="1.0"/>
<skill id="0" multiplier="1.1" /> <!-- fist -->
<skill id="1" multiplier="1.5" />
<skill id="2" multiplier="1.5" />
<skill id="3" multiplier="1.5" />
<skill id="4" multiplier="2.0" />
<skill id="5" multiplier="1.2" />
<skill id="6" multiplier="1.1" />
<gem quality="0" name="lesser spiritualist gem" />
<gem quality="1" name="spiritualist gem" />
<gem quality="2" name="greater spiritualist gem" />
</vocation>
</vocations>
EOF
systemctl start canary
sleep 3
if ! systemctl is-active --quiet canary; then
echo "ERROR: canary service is not running after start!"
systemctl status canary --no-pager || true
exit 1
fi
trap - EXIT
echo "DEPLOY_SUCCESS=true"
# ---------------------------------------------------------
# On failure, fetch the tail of the remote deploy log over a
# second SSH session. This keeps the captured output small
# (well under ARG_MAX and under GitHub's ~65k comment limit)
# without piping the full multi-MB build log through env vars.
# ---------------------------------------------------------
- name: Fetch remote deploy log tail
if: always() && steps.remote.outcome != 'success' && steps.setup.outputs.has_pr == 'true'
id: fetch_log
uses: appleboy/ssh-action@91f3272fc5907f4699dcf59761eb622a07342f5a
with:
host: ${{ secrets.NEEP_HOST }}
username: root
password: ${{ secrets.NEEP_PASS }}
timeout: 60s
command_timeout: 60s
capture_stdout: true
script: tail -c 50000 /tmp/canary-deploy.log 2>/dev/null || echo "(no deploy log on remote)"
# ---------------------------------------------------------
# Success or failure comment
# ---------------------------------------------------------
- name: Post deploy result
if: always() && steps.remote.outcome == 'success' && steps.setup.outputs.has_pr == 'true'
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
with:
issue-number: ${{ steps.setup.outputs.pr_number }}
body: |
✅ **Deploy successful!**
**Branch:** `${{ steps.setup.outputs.name }}`
**Build:** Compiled on remote server
*(Triggered by @${{ steps.setup.outputs.actor }})*
- name: Post failure feedback
if: always() && steps.remote.outcome != 'success' && steps.setup.outputs.has_pr == 'true'
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
with:
issue-number: ${{ steps.setup.outputs.pr_number }}
body: |
❌ **Deploy failed!**
**Branch:** `${{ steps.setup.outputs.name }}`
**Last build output (tail, max 50KB):**
<details><summary>Click to expand</summary>
````
${{ steps.fetch_log.outputs.stdout }}
````
</details>
*(Triggered by @${{ steps.setup.outputs.actor }})*
# ---------------------------------------------------------
# React with emojis
# ---------------------------------------------------------
- name: React success
if: always() && github.event_name == 'issue_comment' && steps.remote.outcome == 'success'
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
with:
comment-id: ${{ github.event.comment.id }}
reactions: hooray
- name: React failure
if: always() && github.event_name == 'issue_comment' && steps.remote.outcome != 'success'
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
with:
comment-id: ${{ github.event.comment.id }}
reactions: "-1"