Initial commit

ZnoteX / Commit #5

Commit Initial commit

Alex Alex committed 01/10/2026 09:20 main Full upload
481 files +128,311 -0
A login.php +217-0 View file
@@ -0,0 +1,217 @@
1+<?php
2+require_once 'engine/init.php';
3+
4+if (lws_is_request()) {
5+ lws_handle();
6+}
7+
8+logged_in_redirect();
9+theme_open();
10+
11+// ---------------------------------------------------------------------------
12+// Step 2 of login: website 2FA v2 (independent of the game engine).
13+//
14+// Reached only after user_login() + the legacy TFS 2FA already succeeded, see
15+// below. The pending account id lives in the session, never in the form, so a
16+// visitor cannot skip straight here with an arbitrary account id.
17+// ---------------------------------------------------------------------------
18+if (isset($_POST['tfa2_code']) && isset($_SESSION['tfa2_pending']['id'])) {
19+ $pending = $_SESSION['tfa2_pending'];
20+
21+ if (!Token::isValid($_POST['token'] ?? null)) {
22+ $errors[] = t('login.token_invalid');
23+ } else if ((int)$pending['until'] < time()) {
24+ unset($_SESSION['tfa2_pending']);
25+ $errors[] = t_default('twofa2.expired', 'That verification step expired. Please log in again.');
26+ } else {
27+ $accountId = (int)$pending['id'];
28+ $code = getValue($_POST['tfa2_code'] ?? null);
29+
30+ if ($code !== false && znote2fa_verify_login_input($accountId, $code)) {
31+ unset($_SESSION['tfa2_pending']);
32+ setSession('user_id', $accountId);
33+ $_SESSION['tfa2_sv'] = znote2fa_session_version($accountId);
34+ Token::generate();
35+
36+ if (!empty($_POST['tfa2_trust']) && (int)znote2fa_v2_config()['trusted_device_days'] > 0) {
37+ znote2fa_trusted_device_issue($accountId);
38+ }
39+
40+ header('Location: myaccount.php');
41+ exit();
42+ }
43+
44+ $errors[] = t_default('twofa2.wrong_code', 'That code is not valid. It may have expired, or you may have mistyped it.');
45+ }
46+}
47+
48+if (isset($_SESSION['tfa2_pending']['id'])) {
49+ $pendingStatus = znote2fa_status((int)$_SESSION['tfa2_pending']['id']);
50+
51+ if (empty($_POST['tfa2_email_sent']) && $pendingStatus['email_otp_enabled'] && !$pendingStatus['totp_enabled']) {
52+ $pendingUser = user_data((int)$_SESSION['tfa2_pending']['id'], 'email', 'name');
53+ if (is_array($pendingUser) && !empty($pendingUser['email'])) {
54+ if (!znote2fa_email_send_code((int)$_SESSION['tfa2_pending']['id'], (string)$pendingUser['email'], (string)($pendingUser['name'] ?? ''))) {
55+ $errors[] = t_default('twofa2.email_delivery_failed', 'The verification e-mail could not be sent. Try again later or use a recovery code.');
56+ }
57+ } else {
58+ $errors[] = t_default('twofa2.email_missing', 'This account has no valid e-mail address. Use a recovery code or contact an administrator.');
59+ }
60+ }
61+ view('login_2fa');
62+ theme_close();
63+ exit();
64+}
65+
66+if (empty($_POST) === false && !isset($_POST['tfa2_code'])) {
67+
68+ if ($config['log_ip']) {
69+ znote_visitor_insert_detailed_data(5);
70+ }
71+
72+ $username = $_POST['username'];
73+ $password = $_POST['password'];
74+
75+ $loginGuardIp = znote_login_guard_ip();
76+ $loginGuardLockedFor = znote_login_guard_lockout_remaining($loginGuardIp);
77+
78+ if ($loginGuardLockedFor > 0) {
79+ $errors[] = t('login.too_many_attempts', ['minutes' => (int)ceil($loginGuardLockedFor / 60)]);
80+ } else if (empty($username) || empty($password)) {
81+ $errors[] = t('login.empty_fields');
82+ } else if (strlen($username) > 32 || strlen($password) > 64) {
83+ $errors[] = t('login.too_long');
84+ } else if (user_exist($username) === false) {
85+ znote_login_guard_record($loginGuardIp, (string)$username, false);
86+ $errors[] = t('login.not_found');
87+ } /*else if (user_activated($username) === false) {
88+ $errors[] = t('login.not_activated');
89+ } */else if (!Token::isValid($_POST['token'] ?? null)) {
90+ $errors[] = t('login.token_invalid');
91+ } else {
92+
93+ // Starting login. Delegated to the server adapter, which knows whether
94+ // this engine identifies an account by name or id and which password
95+ // scheme it uses (see engine/adapter/).
96+ $login = znote_server_adapter()->login($username, $password);
97+ if ($login === false) {
98+ znote_login_guard_record($loginGuardIp, (string)$username, false);
99+ $errors[] = t('login.wrong_combo');
100+ } else {
101+ znote_login_guard_record($loginGuardIp, (string)$username, true);
102+ // Check if user have access to login
103+ $status = false;
104+ if ($config['mailserver']['register']) {
105+ $authenticate = db()->fetchOne(
106+ "SELECT `id` FROM `znote_accounts` WHERE `account_id` = ? AND `active` = 1 LIMIT 1;",
107+ [(int)$login]
108+ );
109+ if ($authenticate !== false) {
110+ $status = true;
111+ } else {
112+ $errors[] = t('login.not_activated');
113+ }
114+ } else $status = true;
115+
116+ if ($status) {
117+ // Regular login success, now lets check authentication token code
118+ if (znote_server_adapter()->supportsLegacyTwoFactor() && $config['twoFactorAuthenticator']) {
119+ require_once("engine/function/rfc6238.php");
120+
121+ // Two factor authentication code / token
122+ $authcode = (isset($_POST['authcode'])) ? getValue($_POST['authcode'] ?? null) : false;
123+
124+ // Load secret values from db
125+ $query = db()->fetchOne(
126+ "SELECT `a`.`secret` AS `secret`, `za`.`secret` AS `znote_secret`
127+ FROM `accounts` AS `a`
128+ INNER JOIN `znote_accounts` AS `za` ON `a`.`id` = `za`.`account_id`
129+ WHERE `a`.`id` = ?
130+ LIMIT 1;",
131+ [(int)$login]
132+ );
133+
134+ if ($query === false) {
135+ $errors[] = t('login.failed_title');
136+ $status = false;
137+
138+ // If account table HAS a secret, we need to validate it
139+ } else if ($query['secret'] !== NULL) {
140+
141+ // Validate the secret first to make sure all is good.
142+ if (TokenAuth6238::verify($query['secret'], $authcode) !== true) {
143+ $errors[] = t('login.2fa_wrong');
144+ $errors[] = t('login.2fa_hint');
145+ $status = false;
146+ }
147+
148+ } else {
149+
150+ // secret from accounts table is null/not set. Perhaps we can activate it:
151+ if ($query['znote_secret'] !== NULL && $authcode !== false && !empty($authcode)) {
152+
153+ // Validate the secret first to make sure all is good.
154+ if (TokenAuth6238::verify($query['znote_secret'], $authcode)) {
155+ // Success, enable the 2FA system
156+ db()->execute(
157+ "UPDATE `accounts` SET `secret` = ? WHERE `id` = ?;",
158+ [$query['znote_secret'], (int)$login]
159+ );
160+ } else {
161+ $errors[] = t('login.2fa_activate_failed');
162+ $errors[] = t('login.2fa_wrong');
163+ $errors[] = t('login.2fa_hint');
164+ $status = false;
165+ }
166+ }
167+ }
168+ } // End tfs 1.0+ with 2FA auth
169+
170+ if ($status) {
171+ if (!znote_session_regenerate()) {
172+ $errors[] = t('login.failed_title');
173+ $status = false;
174+ }
175+ }
176+
177+ if ($status) {
178+ $loginNameRow = user_data($login, 'id', 'name');
179+ $isAdminAccount = has_admin_panel_access(is_array($loginNameRow) ? $loginNameRow : array());
180+
181+ if (znote2fa_required((int)$login, $isAdminAccount) && !znote2fa_trusted_device_check((int)$login)) {
182+ $_SESSION['tfa2_pending'] = array('id' => (int)$login, 'until' => time() + 300);
183+ header('Location: login.php');
184+ exit();
185+ }
186+
187+ setSession('user_id', $login);
188+ $_SESSION['tfa2_sv'] = znote2fa_session_version((int)$login);
189+ Token::generate();
190+
191+ // if IP is not set (etc acc created before Znote AAC was in use)
192+ $znote_data = user_znote_account_data($login, 'ip');
193+ if ($znote_data['ip'] == 0) {
194+ $update_data = array(
195+ 'ip' => getIPLong(),
196+ );
197+ user_update_znote_account($update_data);
198+ }
199+
200+ // Send them to myaccount.php
201+ header('Location: myaccount.php');
202+ exit();
203+ }
204+ }
205+ }
206+ }
207+}
208+
209+if (empty($errors) === false) {
210+ header("HTTP/1.1 401 Not Found");
211+}
212+
213+if (empty($_POST) === true || empty($errors) === false) {
214+ view('login_form');
215+}
216+
217+theme_close(); ?>
A logout.php +6-0 View file
@@ -0,0 +1,6 @@
1+<?php
2+require_once 'engine/init.php';
3+
4+znote_session_destroy();
5+header('Location: index.php');
6+exit;
A Lua/CANARY/Installation Instructions.txt +38-0 View file
@@ -0,0 +1,38 @@
1+Znote AAC server scripts for Canary (opentibiabr) and otservbr-global.
2+
3+WHERE TO PUT THEM
4+ Canary: data/scripts/ (or data-otservbr-global/scripts/ on
5+ an otservbr-global data pack)
6+ otservbr-global: data-otservbr-global/scripts/
7+ Drop the .lua files in there (any sub-folder is fine) and restart the server
8+ or run "/reload scripts".
9+
10+FILES
11+ znoteshop_talkaction.lua !shop - player pulls their website shop orders.
12+ znoteshop_globalevent.lua Auto-delivers the same orders every 30s to online
13+ buyers standing in a protection zone. Use BOTH, or
14+ just the talkaction for manual delivery only.
15+ znote_login.lua Syncs SERVER_NAME / IP / GAME_PORT from config.lua
16+ into znote_global_storage for the AAC login web
17+ service. Needs an up-to-date Znote AAC.
18+ powergamers.lua Daily exp / online-time history for powergamers.php.
19+ Run the ALTER TABLE at the top of the file once.
20+ player_history.lua Per-logout skill snapshots (auto-creates its table).
21+ sync_outfit.lua Marks which full outfits a character owns so
22+ characterprofile.php can show them. storage_value
23+ must match $config['EQ_shower'] in the AAC. The
24+ look type lists are the complete Female / Male sets
25+ from canary data/XML/outfits.xml (127 / 125).
26+
27+COMPATIBILITY NOTES
28+ - Every Canary / otservbr-global protocol (10.98+: otservbr-global 12.x,
29+ Canary 13.x, and the current 15.x) supports outfits, addons, mounts and
30+ houses, so the shop scripts handle order types 1, 5, 6 and 7 unconditionally
31+ - no Game.getClientVersion() guard.
32+ - Canary / otservbr-global already log deaths to `player_deaths` and hand out
33+ first items on their data pack, so the TFS playerdeath.lua / first_items.lua
34+ from Lua/TFS_10 are NOT included here - use the engine's own systems.
35+ - House order type 7 uses house:setHouseOwner(guid, true) (the Canary name).
36+ On plain TFS use Lua/TFS_10 instead - that build calls house:setOwnerGuid().
37+ - Message constants used: MESSAGE_EVENT_ADVANCE (success), MESSAGE_FAILURE
38+ (blocked), MESSAGE_STATUS (info). Swap them if your build differs.
A Lua/CANARY/scripts/player_history.lua +229-0 View file
@@ -0,0 +1,229 @@
1+-- Auto install tables if we dont got them yet (first install)
2+db.query([[
3+ CREATE TABLE IF NOT EXISTS `player_history_skill` (
4+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
5+ `player_id` int(11) NOT NULL,
6+ `lastlogin` bigint(20) unsigned NOT NULL,
7+ `lastlogout` bigint(20) unsigned NOT NULL,
8+ `town_id` int(11) NOT NULL,
9+ `lastip` int(10) unsigned NOT NULL,
10+ `skull` tinyint(1) NOT NULL,
11+ `blessings` tinyint(2) NOT NULL,
12+ `onlinetime` int(11) NOT NULL,
13+ `balance` bigint(20) unsigned NOT NULL,
14+ `level` int(11) NOT NULL,
15+ `experience` bigint(20) NOT NULL,
16+ `maglevel` int(11) NOT NULL,
17+ `skill_fist` int(10) unsigned NOT NULL,
18+ `skill_club` int(10) unsigned NOT NULL,
19+ `skill_sword` int(10) unsigned NOT NULL,
20+ `skill_axe` int(10) unsigned NOT NULL,
21+ `skill_dist` int(10) unsigned NOT NULL,
22+ `skill_shielding` int(10) unsigned NOT NULL,
23+ `skill_fishing` int(10) unsigned NOT NULL,
24+ PRIMARY KEY (`id`),
25+ FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE
26+ ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
27+]])
28+
29+
30+-- Auto populate table if it is empty
31+local resultId = db.storeQuery("SELECT `id` FROM `player_history_skill` LIMIT 1;")
32+if resultId == false then
33+ db.asyncQuery([[
34+ INSERT INTO `player_history_skill` (
35+ `player_id`,
36+ `lastlogin`,
37+ `lastlogout`,
38+ `town_id`,
39+ `lastip`,
40+ `skull`,
41+ `blessings`,
42+ `onlinetime`,
43+ `balance`,
44+ `level`,
45+ `experience`,
46+ `maglevel`,
47+ `skill_fist`,
48+ `skill_club`,
49+ `skill_sword`,
50+ `skill_axe`,
51+ `skill_dist`,
52+ `skill_shielding`,
53+ `skill_fishing`
54+ )
55+ SELECT
56+ `p`.`id` AS `player_id`,
57+ `zp`.`created` AS `lastlogin`,
58+ CASE WHEN `p`.`lastlogout` > 0
59+ THEN `p`.`lastlogout`
60+ ELSE `zp`.`created`
61+ END AS `lastlogout`,
62+ `p`.`town_id`,
63+ `p`.`lastip`,
64+ `p`.`skull`,
65+ `p`.`blessings`,
66+ `p`.`onlinetime`,
67+ `p`.`balance`,
68+ `p`.`level`,
69+ `p`.`experience`,
70+ `p`.`maglevel`,
71+ `p`.`skill_fist`,
72+ `p`.`skill_club`,
73+ `p`.`skill_sword`,
74+ `p`.`skill_axe`,
75+ `p`.`skill_dist`,
76+ `p`.`skill_shielding`,
77+ `p`.`skill_fishing`
78+ FROM `players` AS `p`
79+ INNER JOIN `znote_players` AS `zp`
80+ ON `p`.`id` = `zp`.`player_id`
81+ ORDER BY `zp`.`created`
82+ ]])
83+else
84+ result.free(resultId)
85+end
86+
87+
88+-- Logout event, triggered by logout, and death
89+function historyLogoutEvent(player)
90+ local blessdec = 0
91+ local i = 0
92+ while player:hasBlessing(i+1) do
93+ blessdec = blessdec+2^i
94+ i = i+1
95+ end
96+
97+ local playerGuid = player:getGuid()
98+ db.query([[
99+ INSERT INTO `player_history_skill` (
100+ `player_id`,
101+ `lastlogin`,
102+ `lastlogout`,
103+ `town_id`,
104+ `lastip`,
105+ `skull`,
106+ `blessings`,
107+ `onlinetime`,
108+ `balance`,
109+ `level`,
110+ `experience`,
111+ `maglevel`,
112+ `skill_fist`,
113+ `skill_club`,
114+ `skill_sword`,
115+ `skill_axe`,
116+ `skill_dist`,
117+ `skill_shielding`,
118+ `skill_fishing`
119+ ) VALUES (
120+ ]]..table.concat({
121+ playerGuid,
122+ player:getLastLoginSaved(),
123+ os.time(),
124+ player:getTown():getId(),
125+ player:getIp(),
126+ player:getSkull(),
127+ blessdec,
128+ "(SELECT `onlinetime` FROM `players` WHERE `id`='"..playerGuid.."') + ".. os.time() - player:getLastLoginSaved(),
129+ player:getBankBalance(),
130+ player:getLevel(),
131+ player:getExperience(),
132+ player:getMagicLevel(),
133+ player:getSkillLevel(SKILL_FIST),
134+ player:getSkillLevel(SKILL_CLUB),
135+ player:getSkillLevel(SKILL_SWORD),
136+ player:getSkillLevel(SKILL_AXE),
137+ player:getSkillLevel(SKILL_DISTANCE),
138+ player:getSkillLevel(SKILL_SHIELD),
139+ player:getSkillLevel(SKILL_FISHING)
140+ }, ",")..[[
141+ );
142+ ]])
143+end
144+
145+
146+-- Log player state on logout
147+local player_history_skill = CreatureEvent("player_history_skill")
148+function player_history_skill.onLogout(player)
149+ --print("2-logout["..player:getName().."]")
150+ historyLogoutEvent(player)
151+ return true
152+end
153+player_history_skill:register()
154+
155+
156+-- And on death
157+local player_history_skill_death = CreatureEvent("player_history_skill_death")
158+function player_history_skill_death.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
159+ --print("3-death["..creature:getName().."]")
160+ historyLogoutEvent(Player(creature))
161+end
162+player_history_skill_death:register()
163+
164+
165+-- If this is first login, insert current progress
166+local player_history_skill_login = CreatureEvent("player_history_skill_login")
167+function player_history_skill_login.onLogin(player)
168+ --print("1-login["..player:getName().."]")
169+ player:registerEvent("player_history_skill_death")
170+
171+ local playerGuid = player:getGuid()
172+ local resultId = db.storeQuery("SELECT `id` FROM `player_history_skill` WHERE `player_id`="..playerGuid.." LIMIT 1;")
173+ if resultId == false then
174+ db.query([[
175+ INSERT INTO `player_history_skill` (
176+ `player_id`,
177+ `lastlogin`,
178+ `lastlogout`,
179+ `town_id`,
180+ `lastip`,
181+ `skull`,
182+ `blessings`,
183+ `onlinetime`,
184+ `balance`,
185+ `level`,
186+ `experience`,
187+ `maglevel`,
188+ `skill_fist`,
189+ `skill_club`,
190+ `skill_sword`,
191+ `skill_axe`,
192+ `skill_dist`,
193+ `skill_shielding`,
194+ `skill_fishing`
195+ )
196+ SELECT
197+ `p`.`id` AS `player_id`,
198+ `zp`.`created` AS `lastlogin`,
199+ CASE WHEN `p`.`lastlogout` > 0
200+ THEN `p`.`lastlogout`
201+ ELSE `zp`.`created`
202+ END AS `lastlogout`,
203+ `p`.`town_id`,
204+ `p`.`lastip`,
205+ `p`.`skull`,
206+ `p`.`blessings`,
207+ `p`.`onlinetime`,
208+ `p`.`balance`,
209+ `p`.`level`,
210+ `p`.`experience`,
211+ `p`.`maglevel`,
212+ `p`.`skill_fist`,
213+ `p`.`skill_club`,
214+ `p`.`skill_sword`,
215+ `p`.`skill_axe`,
216+ `p`.`skill_dist`,
217+ `p`.`skill_shielding`,
218+ `p`.`skill_fishing`
219+ FROM `players` AS `p`
220+ INNER JOIN `znote_players` AS `zp`
221+ ON `p`.`id` = `zp`.`player_id`
222+ WHERE `p`.`id` = ]]..playerGuid..[[
223+ ]])
224+ else
225+ result.free(resultId)
226+ end
227+ return true
228+end
229+player_history_skill_login:register()
A Lua/CANARY/scripts/powergamers.lua +65-0 View file
@@ -0,0 +1,65 @@
1+-- getEternalStorage and setEternalStorage
2+-- can be added to data/global.lua if you want to use eternal storage for another purpose than this.
3+-- Regular TFS global storage values get reset every time server reboots. This does not.
4+local function getEternalStorage(key, parser)
5+ local value = result.getString(db.storeQuery("SELECT `value` FROM `znote_global_storage` WHERE `key` = ".. key .. ";"), "value")
6+ if not value then
7+ if parser then
8+ return false
9+ else
10+ return -1
11+ end
12+ end
13+ result.free(value)
14+ return tonumber(value) or value
15+end
16+
17+local function setEternalStorage(key, value)
18+ if getEternalStorage(key, true) then
19+ db.query("UPDATE `znote_global_storage` SET `value` = '".. value .. "' WHERE `key` = ".. key .. ";")
20+ else
21+ db.query("INSERT INTO `znote_global_storage` (`key`, `value`) VALUES (".. key ..", ".. value ..");")
22+ end
23+ return true
24+end
25+
26+-- SQL Query to execute: --
27+--[[
28+ALTER TABLE `znote_players` ADD `exphist_lastexp` BIGINT NOT NULL DEFAULT '0',
29+ADD `exphist1` BIGINT NOT NULL DEFAULT '0',
30+ADD `exphist2` BIGINT NOT NULL DEFAULT '0',
31+ADD `exphist3` BIGINT NOT NULL DEFAULT '0',
32+ADD `exphist4` BIGINT NOT NULL DEFAULT '0',
33+ADD `exphist5` BIGINT NOT NULL DEFAULT '0',
34+ADD `exphist6` BIGINT NOT NULL DEFAULT '0',
35+ADD `exphist7` BIGINT NOT NULL DEFAULT '0',
36+ADD `onlinetimetoday` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0',
37+ADD `onlinetime1` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0',
38+ADD `onlinetime2` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0',
39+ADD `onlinetime3` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0',
40+ADD `onlinetime4` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0',
41+ADD `onlinetime5` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0',
42+ADD `onlinetime6` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0',
43+ADD `onlinetime7` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0',
44+ADD `onlinetimeall` INT UNSIGNED NOT NULL DEFAULT '0';
45+]]--
46+
47+-- after that execute: --
48+--[[
49+UPDATE `znote_players` AS `z` INNER JOIN `players` AS `p` ON `p`.`id`=`z`.`player_id` SET `z`.`exphist_lastexp`=`p`.`experience`;
50+]]--
51+
52+local globalevent = GlobalEvent("PowerGamers")
53+
54+function globalevent.onThink(...)
55+ if tonumber(os.date("%d")) ~= getEternalStorage(23856) then
56+ setEternalStorage(23856, (tonumber(os.date("%d"))))
57+ db.query("UPDATE `znote_players` SET `onlinetime7`=`onlinetime6`, `onlinetime6`=`onlinetime5`, `onlinetime5`=`onlinetime4`, `onlinetime4`=`onlinetime3`, `onlinetime3`=`onlinetime2`, `onlinetime2`=`onlinetime1`, `onlinetime1`=`onlinetimetoday`, `onlinetimetoday`=0;")
58+ db.query("UPDATE `znote_players` `z` INNER JOIN `players` `p` ON `p`.`id`=`z`.`player_id` SET `z`.`exphist7`=`z`.`exphist6`, `z`.`exphist6`=`z`.`exphist5`, `z`.`exphist5`=`z`.`exphist4`, `z`.`exphist4`=`z`.`exphist3`, `z`.`exphist3`=`z`.`exphist2`, `z`.`exphist2`=`z`.`exphist1`, `z`.`exphist1`=`p`.`experience`-`z`.`exphist_lastexp`, `z`.`exphist_lastexp`=`p`.`experience`;")
59+ end
60+ db.query("UPDATE `znote_players` SET `onlinetimetoday` = `onlinetimetoday` + 60, `onlinetimeall` = `onlinetimeall` + 60 WHERE `player_id` IN (SELECT `player_id` FROM `players_online` WHERE `players_online`.`player_id` = `znote_players`.`player_id`)")
61+ return true
62+end
63+
64+globalevent:interval(60000)
65+globalevent:register()
A Lua/CANARY/scripts/sync_outfit.lua +57-0 View file
@@ -0,0 +1,57 @@
1+-- Sync the full outfits (all 3 addons) a character owns into Znote AAC storage,
2+-- so characterprofile.php can show them. Canary / otservbr-global.
3+--
4+-- looktype lists below are the complete Female / Male outfit sets from
5+-- opentibiabr/canary data/XML/outfits.xml. Regenerate them from that file if
6+-- you add custom outfits.
7+
8+znote_outfit_list = {
9+ { -- Female outfits (127)
10+ 136, 137, 138, 139, 140, 141, 142, 147, 148, 149,
11+ 150, 155, 156, 157, 158, 252, 269, 270, 279, 288,
12+ 324, 329, 336, 366, 431, 433, 464, 466, 471, 513,
13+ 514, 542, 575, 578, 618, 620, 632, 635, 636, 664,
14+ 666, 683, 694, 696, 698, 724, 732, 745, 749, 759,
15+ 845, 852, 874, 885, 900, 909, 929, 956, 958, 963,
16+ 965, 967, 969, 971, 973, 975, 1020, 1024, 1043, 1050,
17+ 1057, 1070, 1095, 1103, 1128, 1147, 1162, 1174, 1187, 1203,
18+ 1205, 1207, 1211, 1244, 1246, 1252, 1271, 1280, 1283, 1289,
19+ 1293, 1323, 1332, 1339, 1372, 1383, 1385, 1387, 1416, 1437,
20+ 1445, 1450, 1456, 1461, 1490, 1501, 1569, 1576, 1582, 1598,
21+ 1613, 1619, 1663, 1676, 1681, 1714, 1723, 1726, 1746, 1775,
22+ 1777, 1808, 1825, 1832, 1838, 1860, 1861
23+ },
24+ { -- Male outfits (125)
25+ 128, 129, 130, 131, 132, 133, 134, 143, 144, 145,
26+ 146, 151, 152, 153, 154, 251, 268, 273, 278, 289,
27+ 325, 328, 335, 367, 430, 432, 463, 465, 472, 512,
28+ 516, 541, 574, 577, 610, 619, 633, 634, 637, 665,
29+ 667, 684, 695, 697, 699, 725, 733, 746, 750, 760,
30+ 846, 853, 873, 884, 899, 908, 931, 955, 957, 962,
31+ 964, 966, 968, 970, 972, 974, 1021, 1023, 1042, 1051,
32+ 1056, 1069, 1094, 1102, 1127, 1146, 1161, 1173, 1186, 1202,
33+ 1204, 1206, 1210, 1243, 1245, 1251, 1270, 1279, 1282, 1288,
34+ 1292, 1322, 1331, 1338, 1371, 1382, 1384, 1386, 1415, 1436,
35+ 1444, 1449, 1457, 1460, 1489, 1500, 1568, 1575, 1581, 1597,
36+ 1612, 1618, 1662, 1675, 1680, 1713, 1722, 1725, 1745, 1774,
37+ 1776, 1809, 1824, 1831, 1837
38+ }
39+}
40+
41+local syncOutfit = CreatureEvent("ZnoteSyncOutfit")
42+
43+function syncOutfit.onLogin(player)
44+ -- storage_value .. storage_value + highest look type must be free.
45+ -- Must match $config['EQ_shower'] in the Znote AAC config.php.
46+ local storage_value = 10000
47+ for _, lookType in ipairs(znote_outfit_list[player:getSex() + 1]) do
48+ if player:hasOutfit(lookType, 3) then
49+ if player:getStorageValue(storage_value + lookType) ~= 3 then
50+ player:setStorageValue(storage_value + lookType, 3)
51+ end
52+ end
53+ end
54+ return true
55+end
56+
57+syncOutfit:register()
A Lua/CANARY/scripts/znoteshop_globalevent.lua +104-0 View file
@@ -0,0 +1,104 @@
1+-- Znote Shop auto-delivery for Canary (opentibiabr) / otservbr-global.
2+-- Every 30 seconds it hands pending item / outfit / mount / house orders to the
3+-- buyer if they are online AND standing in a protection zone. Players can still
4+-- force delivery with !shop (see znoteshop_talkaction.lua) - keep both.
5+
6+local znoteShopGlobal = GlobalEvent("ZnoteShopDelivery")
7+
8+function znoteShopGlobal.onThink(interval)
9+ local orders = db.storeQuery([[
10+ SELECT
11+ MIN(`po`.`player_id`) AS `player_id`,
12+ `shop`.`id`,
13+ `shop`.`type`,
14+ `shop`.`itemid`,
15+ `shop`.`count`
16+ FROM `players_online` AS `po`
17+ INNER JOIN `players` AS `p` ON `po`.`player_id` = `p`.`id`
18+ INNER JOIN `znote_shop_orders` AS `shop` ON `p`.`account_id` = `shop`.`account_id`
19+ WHERE `shop`.`type` IN (1, 5, 6, 7)
20+ GROUP BY `shop`.`id`
21+ ]])
22+ if orders == false then
23+ return true
24+ end
25+
26+ repeat
27+ local playerId = result.getNumber(orders, "player_id")
28+ local oId = result.getNumber(orders, "id")
29+ local oType = result.getNumber(orders, "type")
30+ local oItemId = result.getNumber(orders, "itemid")
31+ local oCount = result.getNumber(orders, "count")
32+
33+ local player = Player(playerId)
34+ if player then
35+ local tile = Tile(player:getPosition())
36+ if tile and tile:hasFlag(TILESTATE_PROTECTIONZONE) then
37+
38+ if oType == 1 then
39+ local itemType = ItemType(oItemId)
40+ local needSlots = itemType:isStackable() and (math.floor(oCount / 100) + 1) or oCount
41+ if player:getFreeCapacity() < itemType:getWeight(oCount) then
42+ player:sendTextMessage(MESSAGE_FAILURE, "You have a pending shop order but not enough capacity.")
43+ elseif player:getFreeBackpackSlots() < needSlots then
44+ player:sendTextMessage(MESSAGE_FAILURE, ("You have a pending shop order - free %d backpack slots."):format(needSlots))
45+ else
46+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. oId .. ";")
47+ player:addItem(oItemId, oCount)
48+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, ("You received %dx %s."):format(oCount, itemType:getName()))
49+ end
50+
51+ elseif oType == 5 then
52+ local looks = {}
53+ local packed = oItemId
54+ if packed > 1000 then
55+ local male = math.floor(packed / 1000)
56+ table.insert(looks, male)
57+ packed = packed - (male * 1000)
58+ end
59+ table.insert(looks, packed)
60+ for _, lookType in ipairs(looks) do
61+ if not player:hasOutfit(lookType, oCount) then
62+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. oId .. ";")
63+ player:addOutfit(lookType)
64+ player:addOutfitAddon(lookType, oCount)
65+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received a new outfit.")
66+ else
67+ player:sendTextMessage(MESSAGE_FAILURE, "You already own a purchased outfit and addon.")
68+ end
69+ end
70+
71+ elseif oType == 6 then
72+ if not player:hasMount(oItemId) then
73+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. oId .. ";")
74+ player:addMount(oItemId)
75+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received a new mount.")
76+ else
77+ player:sendTextMessage(MESSAGE_FAILURE, "You already own a purchased mount.")
78+ end
79+
80+ elseif oType == 7 then
81+ local house = House(oItemId)
82+ local buyer = db.storeQuery("SELECT `name` FROM `players` WHERE `id` = " .. oCount .. " LIMIT 1;")
83+ if buyer ~= false then
84+ local buyerName = result.getString(buyer, "name")
85+ result.free(buyer)
86+ if house then
87+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. oId .. ";")
88+ house:setHouseOwner(oCount, true)
89+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, ("House %s now belongs to %s."):format(house:getName(), buyerName))
90+ end
91+ end
92+ end
93+
94+ else
95+ player:sendTextMessage(MESSAGE_STATUS, "You have a pending shop order - stand in a protection zone to receive it.")
96+ end
97+ end
98+ until not result.next(orders)
99+ result.free(orders)
100+ return true
101+end
102+
103+znoteShopGlobal:interval(30000)
104+znoteShopGlobal:register()
A Lua/CANARY/scripts/znoteshop_talkaction.lua +107-0 View file
@@ -0,0 +1,107 @@
1+-- Znote Shop for Znote AAC on Canary (opentibiabr) / otservbr-global.
2+-- Players type !shop while standing in a protection zone to receive the orders
3+-- they bought on the website.
4+--
5+-- Order types (znote_shop_orders.type):
6+-- 1 item id itemid = item id, count = amount
7+-- 2 premium handled by the website
8+-- 3 gender change handled by the website
9+-- 4 name change handled by the website
10+-- 5 outfit + addons itemid = male*1000 + female (or a single look type),
11+-- count = addon bitmask (1, 2 or 3)
12+-- 6 mount itemid = mount id
13+-- 7 instant house itemid = house id, count = buyer player id
14+
15+local znoteShopTalk = TalkAction("!shop")
16+
17+function znoteShopTalk.onSay(player, words, param)
18+ local storage = 54073 -- unused storage, throttles !shop against SQL spam
19+ local cooldown = 15 -- seconds
20+
21+ local remaining = player:getStorageValue(storage) - os.time()
22+ if remaining > 0 then
23+ player:sendTextMessage(MESSAGE_STATUS, ("You can use !shop again in %d seconds."):format(remaining))
24+ return true
25+ end
26+ player:setStorageValue(storage, os.time() + cooldown)
27+
28+ local orders = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. player:getAccountId() .. ";")
29+ if orders == false then
30+ player:sendTextMessage(MESSAGE_STATUS, "You have no orders.")
31+ return true
32+ end
33+
34+ local served = false
35+ repeat
36+ local oId = result.getNumber(orders, "id")
37+ local oType = result.getNumber(orders, "type")
38+ local oItemId = result.getNumber(orders, "itemid")
39+ local oCount = result.getNumber(orders, "count")
40+
41+ if oType == 1 then
42+ served = true
43+ local itemType = ItemType(oItemId)
44+ if player:getFreeCapacity() >= itemType:getWeight(oCount) then
45+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. oId .. ";")
46+ player:addItem(oItemId, oCount)
47+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, ("You received %dx %s."):format(oCount, itemType:getName()))
48+ else
49+ player:sendTextMessage(MESSAGE_FAILURE, "You need more capacity to carry this order.")
50+ end
51+
52+ elseif oType == 5 then
53+ served = true
54+ local looks = {}
55+ local packed = oItemId
56+ if packed > 1000 then
57+ local male = math.floor(packed / 1000)
58+ table.insert(looks, male)
59+ packed = packed - (male * 1000)
60+ end
61+ table.insert(looks, packed)
62+ for _, lookType in ipairs(looks) do
63+ if not player:hasOutfit(lookType, oCount) then
64+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. oId .. ";")
65+ player:addOutfit(lookType)
66+ player:addOutfitAddon(lookType, oCount)
67+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received a new outfit.")
68+ else
69+ player:sendTextMessage(MESSAGE_FAILURE, "You already own this outfit and addon.")
70+ end
71+ end
72+
73+ elseif oType == 6 then
74+ served = true
75+ if not player:hasMount(oItemId) then
76+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. oId .. ";")
77+ player:addMount(oItemId)
78+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received a new mount.")
79+ else
80+ player:sendTextMessage(MESSAGE_FAILURE, "You already own this mount.")
81+ end
82+
83+ elseif oType == 7 then
84+ served = true
85+ local house = House(oItemId)
86+ local buyer = db.storeQuery("SELECT `name` FROM `players` WHERE `id` = " .. oCount .. " LIMIT 1;")
87+ if buyer ~= false then
88+ local buyerName = result.getString(buyer, "name")
89+ result.free(buyer)
90+ if house then
91+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. oId .. ";")
92+ house:setHouseOwner(oCount, true)
93+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, ("House %s now belongs to %s. Keep the rent in the bank."):format(house:getName(), buyerName))
94+ end
95+ end
96+ end
97+ until not result.next(orders)
98+ result.free(orders)
99+
100+ if not served then
101+ player:sendTextMessage(MESSAGE_STATUS, "You have no in-game orders to process.")
102+ end
103+ return true
104+end
105+
106+znoteShopTalk:separator(" ")
107+znoteShopTalk:register()
A Lua/CANARY/scripts/znote_login.lua +68-0 View file
@@ -0,0 +1,68 @@
1+-- Znote LoginWebService (version 1) for protocol 11, 12+
2+-- Move file to this location: data/scripts/znote_login.lua
3+-- And restart OT server, it should auto load script.
4+-- Requires updated version of Znote AAC. (18. June 2020)
5+-- This script will help Znote AAC connect players to this game server.
6+
7+local znote_loginWebService = GlobalEvent("znote_loginWebService")
8+function znote_loginWebService.onStartup()
9+ print(" ")
10+ print("=============================")
11+ print("= Znote AAC loginWebService =")
12+ print("=============================")
13+ local configLua = {
14+ ["SERVER_NAME"] = configManager.getString(configKeys.SERVER_NAME),
15+ ["IP"] = configManager.getString(configKeys.IP),
16+ ["GAME_PORT"] = configManager.getNumber(configKeys.GAME_PORT)
17+ }
18+ local configSQL = {
19+ ["SERVER_NAME"] = false,
20+ ["IP"] = false,
21+ ["GAME_PORT"] = false
22+ }
23+ local webStorage = db.storeQuery([[
24+ SELECT
25+ `key`,
26+ `value`
27+ FROM `znote_global_storage`
28+ WHERE `key` IN('SERVER_NAME', 'IP', 'GAME_PORT')
29+ ]])
30+ if webStorage ~= false then
31+ repeat
32+ local key = result.getString(webStorage, 'key')
33+ local value = result.getString(webStorage, 'value')
34+ configSQL[key] = value
35+ until not result.next(webStorage)
36+ result.free(webStorage)
37+ end
38+ local inserts = {}
39+ if configSQL.SERVER_NAME == false then
40+ table.insert(inserts, "('SERVER_NAME',".. db.escapeString(configLua.SERVER_NAME) ..")")
41+ elseif configSQL.SERVER_NAME ~= configLua.SERVER_NAME then
42+ db.query("UPDATE `znote_global_storage` SET `value`=".. db.escapeString(configLua.SERVER_NAME) .." WHERE `key`='SERVER_NAME';")
43+ print("= Updated [SERVER_NAME] FROM [" .. configSQL.SERVER_NAME .. "] to [" .. configLua.SERVER_NAME .. "]")
44+ end
45+ if configSQL.IP == false then
46+ table.insert(inserts, "('IP',".. db.escapeString(configLua.IP) ..")")
47+ elseif configSQL.IP ~= configLua.IP then
48+ db.query("UPDATE `znote_global_storage` SET `value`=".. db.escapeString(configLua.IP) .." WHERE `key`='IP';")
49+ print("= Updated [IP] FROM [" .. configSQL.IP .. "] to [" .. configLua.IP .. "]")
50+ end
51+ if configSQL.GAME_PORT == false then
52+ table.insert(inserts, "('GAME_PORT',".. db.escapeString(configLua.GAME_PORT) ..")")
53+ elseif configSQL.GAME_PORT ~= tostring(configLua.GAME_PORT) then
54+ db.query("UPDATE `znote_global_storage` SET `value`=".. db.escapeString(configLua.GAME_PORT) .." WHERE `key`='GAME_PORT';")
55+ print("= Updated [GAME_PORT] FROM [" .. configSQL.GAME_PORT .. "] to [" .. configLua.GAME_PORT .. "]")
56+ end
57+ if #inserts > 0 then
58+ db.query("INSERT INTO `znote_global_storage` (`key`,`value`) VALUES "..table.concat(inserts,',')..";")
59+ print("= Fixed " .. #inserts .. " missing configurations.")
60+ end
61+ print("=============================")
62+ print("= SERVER_NAME: " .. configLua.SERVER_NAME)
63+ print("= IP: " .. configLua.IP)
64+ print("= GAME_PORT: " .. configLua.GAME_PORT)
65+ print("=============================")
66+ print(" ")
67+end
68+znote_loginWebService:register()
A Lua/OTHIRE/talkaction shopsystem/talkaction XML.txt +9-0 View file
@@ -0,0 +1,9 @@
1+Add to data/talkactions/talkactions.xml:
2+
3+<talkaction words="!shop" script="znoteshop.lua"/>
4+
5+Then put znoteshop.lua in data/talkactions/scripts/ and restart the server.
6+
7+OTHire (7.72) has no mounts or addons, so only shop order types 1 (items) and
8+5 (outfit look type) are delivered in game. Everything else is handled by the
9+website.
A Lua/OTHIRE/talkaction shopsystem/znoteshop.lua +76-0 View file
@@ -0,0 +1,76 @@
1+-- Znote Shop for Znote AAC on OTHire (7.72).
2+-- Player types !shop to receive the orders bought on the website.
3+--
4+-- OTHire is a 7.72 server: there are no mounts and no addon system, so only
5+-- order type 1 (items) and a plain type 5 (outfit look type, addon ignored by
6+-- the client) are handled here. Premium / gender / name orders (2, 3, 4) are
7+-- applied by the website.
8+
9+function onSay(cid, words, param)
10+ local storage = 54073 -- unused storage, throttles !shop against SQL spam
11+ local cooldown = 15 -- seconds
12+
13+ if getPlayerStorageValue(cid, storage) > os.time() then
14+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "You can use !shop again in " .. (getPlayerStorageValue(cid, storage) - os.time()) .. " seconds.")
15+ return false
16+ end
17+ setPlayerStorageValue(cid, storage, os.time() + cooldown)
18+
19+ local accid = getAccountNumberByPlayerName(getCreatureName(cid))
20+ local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. accid .. ";")
21+ if orderQuery == false then
22+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You have no orders.")
23+ return false
24+ end
25+
26+ local served = false
27+ repeat
28+ local q_id = result.getDataInt(orderQuery, "id")
29+ local q_type = result.getDataInt(orderQuery, "type")
30+ local q_itemid = result.getDataInt(orderQuery, "itemid")
31+ local q_count = result.getDataInt(orderQuery, "count")
32+
33+ -- ORDER TYPE 1 (items)
34+ if q_type == 1 then
35+ served = true
36+ if getPlayerFreeCap(cid) >= getItemWeight(q_itemid, q_count) then
37+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";")
38+ doPlayerAddItem(cid, q_itemid, q_count)
39+ doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received " .. q_count .. " " .. getItemName(q_itemid) .. "(s)!")
40+ else
41+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You need more capacity to carry this order!")
42+ end
43+ end
44+
45+ -- ORDER TYPE 5 (outfit look type; addon bit is ignored on 7.72)
46+ if q_type == 5 then
47+ served = true
48+ local itemid = q_itemid
49+ local outfits = {}
50+ if itemid > 1000 then
51+ local first = math.floor(itemid / 1000)
52+ table.insert(outfits, first)
53+ itemid = itemid - (first * 1000)
54+ end
55+ table.insert(outfits, itemid)
56+
57+ for _, outfitId in pairs(outfits) do
58+ if not canPlayerWearOutfit(cid, outfitId, q_count) then
59+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";")
60+ doPlayerAddOutfit(cid, outfitId, q_count)
61+ doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!")
62+ else
63+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this outfit!")
64+ end
65+ end
66+ end
67+
68+ -- Type 6 (mounts) and 7 (houses) do not exist on 7.72 / OTHire.
69+ until not result.next(orderQuery)
70+ result.free(orderQuery)
71+
72+ if not served then
73+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.")
74+ end
75+ return false
76+end
A Lua/TFS_02/creaturescript firstitems/firstitems.lua +77-0 View file
@@ -0,0 +1,77 @@
1+function onLogin(cid)
2+ local storage = 30055 -- storage value
3+
4+ local sorcItems = {
5+ 2460, -- Brass helmet
6+ 2465, -- Brass armor
7+ 2190, -- Wand of vortex
8+ 2511, -- Brass shield
9+ 2478, -- Brass legs
10+ 2643, -- Leather boots
11+ 1988, -- Brown backpack
12+ 2050 -- torch
13+ }
14+ local druidItems = {
15+ 2460, -- Brass helmet
16+ 2465, -- Brass armor
17+ 2511, -- Brass shield
18+ 2182, -- Snakebite rod
19+ 2478, -- Brass legs
20+ 2643, -- Leather boots
21+ 1988, -- Brown backpack
22+ 2050 -- torch
23+ }
24+ local pallyItems = {
25+ 2460, -- Brass helmet
26+ 2465, -- Brass armor
27+ 2456, -- Bow
28+ 2478, -- Brass legs
29+ 2643, -- Leather boots
30+ 1988, -- Brown backpack
31+ }
32+ local kinaItems = {
33+ 2460, -- Brass helmet
34+ 2465, -- Brass armor
35+ 2511, -- Brass shield
36+ 2412, -- Katana
37+ 2478, -- Brass legs
38+ 2643, -- Leather boots
39+ 1988, -- Brown backpack
40+ 2050 -- torch
41+ }
42+
43+ if getPlayerStorageValue(cid, storage) == -1 then
44+ setPlayerStorageValue(cid, storage, 1)
45+ if getPlayerVocation(cid) == 1 then
46+ -- Sorcerer
47+ for i = 1, table.getn(sorcItems), 1 do
48+ doPlayerAddItem(cid, sorcItems[i], 1, false)
49+ end
50+
51+ elseif getPlayerVocation(cid) == 2 then
52+ -- Druid
53+ for i = 1, table.getn(druidItems), 1 do
54+ doPlayerAddItem(cid, druidItems[i], 1, false)
55+ end
56+
57+ elseif getPlayerVocation(cid) == 3 then
58+ -- Paladin
59+ for i = 1, table.getn(pallyItems), 1 do
60+ doPlayerAddItem(cid, pallyItems[i], 1, false)
61+ end
62+ -- 8 arrows
63+ doPlayerAddItem(cid, 2544, 8, false)
64+
65+ elseif getPlayerVocation(cid) == 4 then
66+ -- Knight
67+ for i = 1, table.getn(kinaItems), 1 do
68+ doPlayerAddItem(cid, kinaItems[i], 1, false)
69+ end
70+ end
71+
72+ -- Common for all
73+ doPlayerAddItem(cid, 2674, 5, false) -- 5 apples
74+ doPlayerAddItem(cid, 2120, 1, false) -- 1 rope
75+ end
76+ return true
77+end
A Lua/TFS_02/creaturescript firstitems/Installation Instructions.txt +4-0 View file
@@ -0,0 +1,4 @@
1+Step 1: Copy firstitems.lua to /data/creaturescripts/scripts/ folder
2+-- Edit firstitems.lua with item IDs you want characters to start with on your server.
3+
4+Step 2: Restart OT server, and it should work. :)
A Lua/TFS_02/talkaction shopsystem/talkaction XML.txt +1-0 View file
@@ -0,0 +1,1 @@
1+<talkaction words="!shop" script="znoteshop.lua"/>
A Lua/TFS_02/talkaction shopsystem/znoteshop.lua +103-0 View file
@@ -0,0 +1,103 @@
1+-- Znote Shop v1.1 for Znote AAC on TFS 0.2.13+ Mystic Spirit.
2+function onSay(cid, words, param)
3+ local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks.
4+ local cooldown = 15 -- in seconds.
5+
6+ if getPlayerStorageValue(cid, storage) <= os.time() then
7+ setPlayerStorageValue(cid, storage, os.time() + cooldown)
8+ local accid = getAccountNumberByPlayerName(getCreatureName(cid))
9+
10+ local type_desc = {
11+ "itemids",
12+ "pending premium (skip)",
13+ "pending gender change (skip)",
14+ "pending character name change (skip)",
15+ "Outfit and addons",
16+ "Mounts",
17+ "Instant house purchase"
18+ }
19+ print("Player: " .. getCreatureName(cid) .. " triggered !shop talkaction.")
20+ -- Create the query
21+ local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. accid .. ";")
22+ local served = false
23+
24+ -- Detect if we got any results
25+ if orderQuery ~= false then
26+ -- Fetch order values
27+ local q_id = result.getDataInt(orderQuery, "id")
28+ local q_type = result.getDataInt(orderQuery, "type")
29+ local q_itemid = result.getDataInt(orderQuery, "itemid")
30+ local q_count = result.getDataInt(orderQuery, "count")
31+
32+ local description = "Unknown or custom type"
33+ if type_desc[q_type] ~= nil then
34+ description = type_desc[q_type]
35+ end
36+ print("Processing type "..q_type..": ".. description)
37+
38+ -- ORDER TYPE 1 (Regular item shop products)
39+ if q_type == 1 then
40+ served = true
41+ -- Get weight
42+ local playerCap = getPlayerFreeCap(cid)
43+ local itemweight = getItemWeight(q_itemid, q_count)
44+ if playerCap >= itemweight then
45+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";")
46+ doPlayerAddItem(cid, q_itemid, q_count)
47+ doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received ".. q_count .." "..getItemName(q_itemid).."(s)!")
48+ else
49+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "Need more CAP!")
50+ end
51+ end
52+ -- ORDER TYPE 5 (Outfit and addon)
53+ if q_type == 5 then
54+ served = true
55+
56+ local itemid = q_itemid
57+ local outfits = {}
58+
59+ if itemid > 1000 then
60+ local first = math.floor(itemid/1000)
61+ table.insert(outfits, first)
62+ itemid = itemid - (first * 1000)
63+ end
64+ table.insert(outfits, itemid)
65+
66+ for _, outfitId in pairs(outfits) do
67+ -- Make sure player don't already have this outfit and addon
68+ if not canPlayerWearOutfit(cid, outfitId, q_count) then
69+ db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";")
70+ doPlayerAddOutfit(cid,outfitId,q_count)
71+ doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!")
72+ else
73+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this outfit and addon!")
74+ end
75+ end
76+ end
77+
78+ -- ORDER TYPE 6 (Mounts)
79+ -- Not supported on TFS 0.2
80+
81+ -- Add custom order types here
82+ -- Type 1 is for itemids (Already coded here)
83+ -- Type 2 is for premium (Coded on web)
84+ -- Type 3 is for gender change (Coded on web)
85+ -- Type 4 is for character name change (Coded on web)
86+ -- Type 5 is for character outfit and addon (Already coded here)
87+ -- Type 6 is for mounts (Not for TFS 0.2)
88+ -- Type 7 is for Instant house purchase (Not for TFS 0.2)
89+ -- So use type 8+ for custom stuff, like etc packages.
90+ -- if q_type == 8 then
91+ -- end
92+ result.free(orderQuery)
93+ if not served then
94+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.")
95+ end
96+ else
97+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You have no orders.")
98+ end
99+ else
100+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every "..cooldown.." seconds. Remaining cooldown: ".. getPlayerStorageValue(cid, storage) - os.time())
101+ end
102+ return false
103+end
A Lua/TFS_03/creaturescript firstitems/firstitems.lua +77-0 View file
@@ -0,0 +1,77 @@
1+function onLogin(cid)
2+ local storage = 30055 -- storage value
3+
4+ local sorcItems = {
5+ 2460, -- Brass helmet
6+ 2465, -- Brass armor
7+ 2190, -- Wand of vortex
8+ 2511, -- Brass shield
9+ 2478, -- Brass legs
10+ 2643, -- Leather boots
11+ 1988, -- Brown backpack
12+ 2050 -- torch
13+ }
14+ local druidItems = {
15+ 2460, -- Brass helmet
16+ 2465, -- Brass armor
17+ 2511, -- Brass shield
18+ 2182, -- Snakebite rod
19+ 2478, -- Brass legs
20+ 2643, -- Leather boots
21+ 1988, -- Brown backpack
22+ 2050 -- torch
23+ }
24+ local pallyItems = {
25+ 2460, -- Brass helmet
26+ 2465, -- Brass armor
27+ 2456, -- Bow
28+ 2478, -- Brass legs
29+ 2643, -- Leather boots
30+ 1988, -- Brown backpack
31+ }
32+ local kinaItems = {
33+ 2460, -- Brass helmet
34+ 2465, -- Brass armor
35+ 2511, -- Brass shield
36+ 2412, -- Katana
37+ 2478, -- Brass legs
38+ 2643, -- Leather boots
39+ 1988, -- Brown backpack
40+ 2050 -- torch
41+ }
42+
43+ if getPlayerStorageValue(cid, storage) == -1 then
44+ setPlayerStorageValue(cid, storage, 1)
45+ if getPlayerVocation(cid) == 1 then
46+ -- Sorcerer
47+ for i = 1, table.getn(sorcItems), 1 do
48+ doPlayerAddItem(cid, sorcItems[i], 1, false)
49+ end
50+
51+ elseif getPlayerVocation(cid) == 2 then
52+ -- Druid
53+ for i = 1, table.getn(druidItems), 1 do
54+ doPlayerAddItem(cid, druidItems[i], 1, false)
55+ end
56+
57+ elseif getPlayerVocation(cid) == 3 then
58+ -- Paladin
59+ for i = 1, table.getn(pallyItems), 1 do
60+ doPlayerAddItem(cid, pallyItems[i], 1, false)
61+ end
62+ -- 8 arrows
63+ doPlayerAddItem(cid, 2544, 8, false)
64+
65+ elseif getPlayerVocation(cid) == 4 then
66+ -- Knight
67+ for i = 1, table.getn(kinaItems), 1 do
68+ doPlayerAddItem(cid, kinaItems[i], 1, false)
69+ end
70+ end
71+
72+ -- Common for all
73+ doPlayerAddItem(cid, 2674, 5, false) -- 5 apples
74+ doPlayerAddItem(cid, 2120, 1, false) -- 1 rope
75+ end
76+ return true
77+end
A Lua/TFS_03/creaturescript firstitems/Installation Instructions.txt +10-0 View file
@@ -0,0 +1,10 @@
1+Step 1: Copy firstitems.lua to /data/creaturescripts/scripts/ folder
2+-- Edit firstitems.lua with item IDs you want characters to start with on your server.
3+
4+Step 2: Edit the /data/creaturescripts/creaturescripts.XML file
5+ - ADD: <event type="login" name="firstItems" event="script" value="firstitems.lua"/>
6+
7+Step 3: Edit the /data/creaturescripts/scripts/login.lua file
8+ - ADD: registerCreatureEvent(cid, "firstItems")
9+
10+Step 4: Restart OT server, and it should work. :)
A Lua/TFS_03/creaturescript sync outfits/Installation Instructions.txt +7-0 View file
@@ -0,0 +1,7 @@
1+1. Add below line to XML file: data/creaturescripts/creaturescripts.xml
2+<event type="login" name="znote_syncoutfits" event="script" value="syncoutfit.lua"/>
3+
4+2. Register event in login.lua: data/creaturescripts/scripts/login.lua
5+registerCreatureEvent(cid, "znote_syncoutfits")
6+
7+3. Place Lua file syncoutfit.lua in folder: data/creaturescripts/scripts/
A Lua/TFS_03/creaturescript sync outfits/syncoutfit.lua +39-0 View file
@@ -0,0 +1,39 @@
1+-- Sync outfits that player own with Znote AAC
2+-- So its possible to see which full sets player
3+-- has in characterprofile.php
4+
5+znote_outfit_list = {
6+ { -- Female (girl) outfits
7+ 136,137,138,139,140,141,142,147,148,
8+ 149,150,155,156,157,158,252,269,270,
9+ 279,288,324,329,336,366,431,433,464,
10+ 466,471,513,514,542,575,578,618,620,
11+ 632,635,636,664,666,683,694,696,698,
12+ 724,732,745,749,759,845,852,874,885,
13+ 900
14+ },
15+ { -- Male (boy) outfits
16+ 128,129,130,131,132,133,134,143,144,
17+ 145,146,151,152,153,154,251,268,273,
18+ 278,289,325,328,335,367,430,432,463,
19+ 465,472,512,516,541,574,577,610,619,
20+ 633,634,637,665,667,684,695,697,699,
21+ 725,733,746,750,760,846,853,873,884,
22+ 899
23+ }
24+}
25+
26+function onLogin(cid)
27+ -- storage_value + 1000 storages (highest outfit id) must not be used in other script.
28+ -- Must be identical to Znote AAC config.php: $config['EQ_shower'] -> storage_value
29+ local storage_value = 10000
30+ -- Loop through outfits
31+ for _, outfit in pairs(znote_outfit_list[getPlayerSex(cid)+1]) do
32+ if canPlayerWearOutfit(cid,outfit,3) then
33+ if getPlayerStorageValue(cid,storage_value + outfit) ~= 3 then
34+ setPlayerStorageValue(cid,storage_value + outfit, 3)
35+ end
36+ end
37+ end
38+ return true
39+end
A Lua/TFS_03/talkaction shopsystem/Alternatives/znoteshop.lua +118-0 View file
@@ -0,0 +1,118 @@
1+-- Znote Shop v1.1 for Znote AAC on TFS 0.3.6+ Crying Damson. [Alternative]
2+function onSay(cid, words, param)
3+ local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks.
4+ local cooldown = 15 -- in seconds.
5+
6+ if getPlayerStorageValue(cid, storage) <= os.time() then
7+ setPlayerStorageValue(cid, storage, os.time() + cooldown)
8+ local accid = getAccountNumberByPlayerName(getCreatureName(cid))
9+
10+ local type_desc = {
11+ "itemids",
12+ "pending premium (skip)",
13+ "pending gender change (skip)",
14+ "pending character name change (skip)",
15+ "Outfit and addons",
16+ "Mounts",
17+ "Instant house purchase"
18+ }
19+ print("Player: " .. getCreatureName(cid) .. " triggered !shop talkaction.")
20+ -- Create the query
21+ local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. accid .. ";")
22+ local served = false
23+
24+ -- Detect if we got any results
25+ if orderQuery ~= false then
26+ repeat
27+ -- Fetch order values
28+ local q_id = result.getDataInt(orderQuery, "id")
29+ local q_type = result.getDataInt(orderQuery, "type")
30+ local q_itemid = result.getDataInt(orderQuery, "itemid")
31+ local q_count = result.getDataInt(orderQuery, "count")
32+
33+ local description = "Unknown or custom type"
34+ if type_desc[q_type] ~= nil then
35+ description = type_desc[q_type]
36+ end
37+ print("Processing type "..q_type..": ".. description)
38+
39+ -- ORDER TYPE 1 (Regular item shop products)
40+ if q_type == 1 then
41+ served = true
42+ -- Get wheight
43+ local playerCap = getPlayerFreeCap(cid)
44+ local itemweight = getItemWeightById(q_itemid, q_count)
45+ if playerCap >= itemweight then
46+ local delete = db.storeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";")
47+ result.free(delete)
48+ doPlayerAddItem(cid, q_itemid, q_count)
49+ doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have recieved ".. q_count .." "..getItemNameById(q_itemid).."(s)!")
50+ else
51+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "Need more CAP!")
52+ end
53+ end
54+ -- ORDER TYPE 5 (Outfit and addon)
55+ if q_type == 5 then
56+ served = true
57+
58+ local itemid = q_itemid
59+ local outfits = {}
60+
61+ if itemid > 1000 then
62+ local first = math.floor(itemid/1000)
63+ table.insert(outfits, first)
64+ itemid = itemid - (first * 1000)
65+ end
66+ table.insert(outfits, itemid)
67+
68+ for _, outfitId in pairs(outfits) do
69+ -- Make sure player don't already have this outfit and addon
70+ if not canPlayerWearOutfit(cid, outfitId, q_count) then
71+ local delete = db.storeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";")
72+ result.free(delete)
73+ doPlayerAddOutfit(cid,outfitId,q_count)
74+ doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!")
75+ else
76+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this outfit and addon!")
77+ end
78+ end
79+ end
80+
81+ -- ORDER TYPE 6 (Mounts)
82+ if q_type == 6 then
83+ served = true
84+ -- Make sure player don't already have this outfit and addon
85+ if not getPlayerMount(cid, q_itemid) then -- Failed to find a proper hasMount 0.3 function?
86+ local delete = db.storeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";")
87+ result.free(delete)
88+ doPlayerAddMount(cid, q_itemid)
89+ doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!")
90+ else
91+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this mount!")
92+ end
93+ end
94+
95+ -- Add custom order types here
96+ -- Type 1 is for itemids (Already coded here)
97+ -- Type 2 is for premium (Coded on web)
98+ -- Type 3 is for gender change (Coded on web)
99+ -- Type 4 is for character name change (Coded on web)
100+ -- Type 5 is for character outfit and addon (Already coded here)
101+ -- Type 6 is for mounts (Already coded here)
102+ -- Type 7 is for Instant house purchase (Not for TFS 0.3)
103+ -- So use type 8+ for custom stuff, like etc packages.
104+ -- if q_type == 8 then
105+ -- end
106+ until not result.next(orderQuery)
107+ result.free(orderQuery)
108+ if not served then
109+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.")
110+ end
111+ else
112+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You have no orders.")
113+ end
114+ else
115+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every "..cooldown.." seconds. Remaining cooldown: ".. getPlayerStorageValue(cid, storage) - os.time())
116+ end
117+ return false
118+end
A Lua/TFS_03/talkaction shopsystem/talkaction XML.txt +1-0 View file
@@ -0,0 +1,1 @@
1+<talkaction words="!shop" event="script" value="znoteshop.lua"/>
A Lua/TFS_03/talkaction shopsystem/znoteshop.lua +128-0 View file
@@ -0,0 +1,128 @@
1+-- Znote Shop v1.1 for Znote AAC on TFS 0.3.6+ Crying Damson.
2+function onSay(cid, words, param)
3+ local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks.
4+ local cooldown = 15 -- in seconds.
5+
6+ if getPlayerStorageValue(cid, storage) <= os.time() then
7+ setPlayerStorageValue(cid, storage, os.time() + cooldown)
8+ local accid = getAccountNumberByPlayerName(getCreatureName(cid))
9+
10+ local type_desc = {
11+ "itemids",
12+ "pending premium (skip)",
13+ "pending gender change (skip)",
14+ "pending character name change (skip)",
15+ "Outfit and addons",
16+ "Mounts",
17+ "Instant house purchase"
18+ }
19+ print("Player: " .. getCreatureName(cid) .. " triggered !shop talkaction.")
20+ -- Create the query
21+ local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. accid .. ";")
22+ local served = false
23+
24+ -- Detect if we got any results
25+ if orderQuery ~= false then
26+ repeat
27+ -- Fetch order values
28+ local q_id = result.getDataInt(orderQuery, "id")
29+ local q_type = result.getDataInt(orderQuery, "type")
30+ local q_itemid = result.getDataInt(orderQuery, "itemid")
31+ local q_count = result.getDataInt(orderQuery, "count")
32+
33+ local description = "Unknown or custom type"
34+ if type_desc[q_type] ~= nil then
35+ description = type_desc[q_type]
36+ end
37+ print("Processing type "..q_type..": ".. description)
38+
39+ -- ORDER TYPE 1 (Regular item shop products)
40+ if q_type == 1 then
41+ served = true
42+ -- Get weight
43+ local playerCap = getPlayerFreeCap(cid)
44+ local itemweight = getItemWeightById(q_itemid, q_count)
45+ if playerCap >= itemweight and getTileInfo(getCreaturePosition(cid)).protection then
46+ -- backpack check
47+ local backpack = getPlayerSlotItem(cid, 3)
48+ local gotItem = false
49+ if(backpack and backpack.itemid > 0) then
50+ local received = doAddContainerItem(getPlayerSlotItem(cid, 3).uid, q_itemid,q_count)
51+ if(received ~= false) then
52+ db.executeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";")
53+ doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received ".. q_count .." "..getItemNameById(q_itemid).."(s)!")
54+ gotItem = true
55+ end
56+ end
57+
58+ if(not gotItem) then
59+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You have no available space in backpack to receive that item.")
60+ end
61+ else
62+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "Need more CAP and Need ProtectZone!")
63+ end
64+ end
65+ -- ORDER TYPE 5 (Outfit and addon)
66+ if q_type == 5 then
67+ served = true
68+
69+ local itemid = q_itemid
70+ local outfits = {}
71+
72+ if itemid > 1000 then
73+ local first = math.floor(itemid/1000)
74+ table.insert(outfits, first)
75+ itemid = itemid - (first * 1000)
76+ end
77+ table.insert(outfits, itemid)
78+
79+ for _, outfitId in pairs(outfits) do
80+ -- Make sure player don't already have this outfit and addon
81+ if not canPlayerWearOutfit(cid, outfitId, q_count) then
82+ db.executeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";")
83+ doPlayerAddOutfit(cid,outfitId,q_count)
84+ doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!")
85+ else
86+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this outfit and addon!")
87+ end
88+ end
89+ end
90+
91+ -- ORDER TYPE 6 (Mounts)
92+ if q_type == 6 then
93+ served = true
94+ -- Make sure player don't already have this outfit and addon
95+ if not getPlayerMount(cid, q_itemid) then -- Failed to find a proper hasMount 0.3 function?
96+ db.executeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";")
97+ doPlayerAddMount(cid, q_itemid)
98+ doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!")
99+ else
100+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this mount!")
101+ end
102+ end
103+
104+ -- Add custom order types here
105+ -- Type 1 is for itemids (Already coded here)
106+ -- Type 2 is for premium (Coded on web)
107+ -- Type 3 is for gender change (Coded on web)
108+ -- Type 4 is for character name change (Coded on web)
109+ -- Type 5 is for character outfit and addon (Already coded here)
110+ -- Type 6 is for mounts (Already coded here)
111+ -- Type 7 is for Instant house purchase (Not for TFS 0.3)
112+ -- So use type 8+ for custom stuff, like etc packages.
113+ -- if q_type == 8 then
114+ -- end
115+ until not result.next(orderQuery)
116+ result.free(orderQuery)
117+ if not served then
118+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.")
119+ end
120+ else
121+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders.")
122+ end
123+
124+ else
125+ doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every "..cooldown.." seconds. Remaining cooldown: ".. getPlayerStorageValue(cid, storage) - os.time())
126+ end
127+ return false
128+end
A Lua/TFS_10/creaturescript firstitems/firstitems.lua +114-0 View file
@@ -0,0 +1,114 @@
1+-- With Rookgaard
2+
3+--[[
4+local firstItems = {2050, 2382} -- torch and club
5+
6+function onLogin(player)
7+ if player:getLastLoginSaved() <= 0 then
8+ for i = 1, #firstItems do
9+ player:addItem(firstItems[i], 1)
10+ end
11+ player:addItem(player:getSex() == 0 and 2651 or 2650, 1) -- coat
12+ player:addItem(ITEM_BAG, 1)
13+ player:addItem(2674, 1) -- red apple
14+ end
15+ return true
16+end
17+]]--
18+
19+-- Without Rookgaard
20+local config = {
21+ [1] = { -- Sorcerer
22+ items = {
23+ {2175, 1}, -- spellbook
24+ {2190, 1}, -- wand of vortex
25+ {8819, 1}, -- magician's robe
26+ {8820, 1}, -- mage hat
27+ {2468, 1}, -- studded legs
28+ {2643, 1}, -- leather boots
29+ {2661, 1} -- scarf
30+ },
31+ container = {
32+ {2120, 1}, -- rope
33+ {2554, 1}, -- shovel
34+ {7620, 1} -- mana potion
35+ }
36+ },
37+ [2] = { -- Druid
38+ items = {
39+ {2175, 1}, -- spellbook
40+ {2182, 1}, -- snakebite rod
41+ {8819, 1}, -- magician's robe
42+ {8820, 1}, -- mage hat
43+ {2468, 1}, -- studded legs
44+ {2643, 1}, -- leather boots
45+ {2661, 1} -- scarf
46+ },
47+ container = {
48+ {2120, 1}, -- rope
49+ {2554, 1}, -- shovel
50+ {7620, 1} -- mana potion
51+ }
52+ },
53+ [3] = { -- Paladin
54+ items = {
55+ {2525, 1}, -- dwarven shield
56+ {2389, 5}, -- 5 spears
57+ {2660, 1}, -- ranger's cloak
58+ {8923, 1}, -- ranger legs
59+ {2643, 1}, -- leather boots
60+ {2661, 1}, -- scarf
61+ {2480, 1} -- legion helmet
62+ },
63+ container = {
64+ {2120, 1}, -- rope
65+ {2554, 1}, -- shovel
66+ {7618, 1}, -- health potion
67+ {2456, 1}, -- bow
68+ {2544, 50} -- 50 arrows
69+ }
70+ },
71+ [4] = { -- Knight
72+ items = {
73+ {2525, 1}, -- dwarven shield
74+ {8601, 1}, -- steel axe
75+ {2465, 1}, -- brass armor
76+ {2460, 1}, -- brass helmet
77+ {2478, 1}, -- brass legs
78+ {2643, 1}, -- leather boots
79+ {2661, 1} -- scarf
80+ },
81+ container = {
82+ {8602, 1}, -- jagged sword
83+ {2439, 1}, -- daramanian mace
84+ {2120, 1}, -- rope
85+ {2554, 1}, -- shovel
86+ {7618, 1} -- health potion
87+ }
88+ }
89+}
90+
91+function onLogin(player)
92+ local targetVocation = config[player:getVocation():getId()]
93+ if not targetVocation then
94+ return true
95+ end
96+
97+ if player:getLastLoginSaved() ~= 0 then
98+ return true
99+ end
100+
101+ for i = 1, #targetVocation.items do
102+ player:addItem(targetVocation.items[i][1], targetVocation.items[i][2])
103+ end
104+
105+ local backpack = player:addItem(1988)
106+ if not backpack then
107+ return true
108+ end
109+
110+ for i = 1, #targetVocation.container do
111+ backpack:addItem(targetVocation.container[i][1], targetVocation.container[i][2])
112+ end
113+ return true
114+end
A Lua/TFS_10/creaturescript firstitems/Installation Instructions.txt +4-0 View file
@@ -0,0 +1,4 @@
1+Step 1: Copy firstitems.lua to /data/creaturescripts/scripts/ folder
2+-- Edit firstitems.lua with item IDs you want characters to start with on your server.
3+
4+Step 2: Restart OT server, and it should work. :)
A Lua/TFS_10/creaturescript playerdeath/Installation Instructions.txt +3-0 View file
@@ -0,0 +1,3 @@
1+Step 1: Replace the one that comes with tfs with this one
2+
3+Step 2: Restart OT server, and it should work. :)
Top