Compare commits

...

421 Commits

Author SHA1 Message Date
P0nk
d9d2da1f35 Merge branch 'master' into feat/postgresql-database
# Conflicts:
#	database/sql/1-db_database.sql
#	docker-compose.yml
#	pom.xml
#	scripts/npc/mapleTV.js
#	src/main/java/net/server/Server.java
2025-07-22 21:21:05 +02:00
Ponk
9101cd189c Merge pull request #311 from P0nk/cleanup/custom-npc-scripts #minor
Remove custom NPC scripts
2025-07-22 14:57:28 +02:00
P0nk
196e3efec7 Remove support for custom NPC 2025-07-22 14:48:31 +02:00
P0nk
7adce5e2af Remove custom NPC script: Agent E accessory crafter 2025-07-22 14:41:19 +02:00
P0nk
c9aa049874 Remove custom NPC script: Coco chaos synthesizer 2025-07-22 14:38:44 +02:00
P0nk
58d6603946 Remove custom NPC script: Asia shop 2025-07-22 14:34:01 +02:00
P0nk
bfd9f68805 Remove custom NPC script: Dalair equipment merge 2025-07-22 14:29:58 +02:00
P0nk
44c2b77595 Remove custom NPC script: Donation Box bulk selling 2025-07-22 14:25:11 +02:00
P0nk
5148d76f9f Remove custom NPC script: T-1337 shop 2025-07-22 14:18:32 +02:00
P0nk
45bfc395be Remove custom NPC script: Spiegelmann ore refining 2025-07-22 14:06:52 +02:00
P0nk
8052814028 Remove custom NPC script: scroll generator 2025-07-22 14:06:51 +02:00
Ponk
1eeb2483bf Merge pull request #309 from P0nk/feat/liquibase #major
Use Liquibase for automated database schema & data migrations
2025-07-22 10:44:04 +02:00
P0nk
2aabeb378c Tune global drops
Global drops are too common, and the ones we have here are not part of vanilla v83
2025-07-22 10:36:36 +02:00
P0nk
7054d37b58 Remove duplicate items from the GM shop 2025-07-22 10:24:26 +02:00
P0nk
64b20aa659 Remove test table 2025-07-14 21:46:42 +02:00
P0nk
42bb664add Support running additional changelogs at the end
In case you want to add your own custom stuff or change drops or something else.
2025-07-14 21:41:39 +02:00
P0nk
97ed3d2da5 Split into multiple changelogs 2025-07-14 20:32:43 +02:00
P0nk
e2ac5e448b Switch from manual sql scripts to automated scripts with Liquibase 2025-07-06 19:49:54 +02:00
P0nk
2365458b78 Add admin account 2025-07-06 19:02:46 +02:00
P0nk
c807c1e54f Remove custom shops 2025-07-06 17:56:49 +02:00
P0nk
6d647e8010 Add Rien shops' items 2025-07-06 17:50:06 +02:00
P0nk
1dce65d146 Add GM shop items 2025-07-06 17:48:25 +02:00
P0nk
8ceb60329d Add missing equips in Singapore shops 2025-07-06 17:36:01 +02:00
P0nk
f240c7e98b Remove duplicate drops
Some of these duplicate drops should be kept,
but are removed for the sake of efficiency.

For example, Stumpy (3220000) should drop multiple Tree Branch (4000003) and Leaf (4000005), but the duplicate have been removed.
To fix this, one has to go through the removed drops in this commit and manually reintroduce the drops that should have been kept. The "RemoveDuplicateDrops" tool simplifies this a little bit as it produces a file with all the removed drops. As of writing this, it removed duplicate drops from 415 mobs, for a total of 7331 removed drops.

The following SQL query finds the amount of duplicate drops in the database:
SELECT COUNT(*) total_mobs_with_duplicate_items, SUM(extra_item_drops) AS total_duplicate_items
FROM (SELECT dropperid, SUM(extras) AS extra_item_drops
	FROM
		(SELECT dropperid, itemid, COUNT(*) - 1 AS extras
		FROM lb_drop_data
		GROUP BY dropperid, itemid
		HAVING COUNT(*) > 1) AS i
		GROUP BY dropperid
		ORDER BY dropperid
) AS i2
2025-07-06 14:47:02 +02:00
P0nk
f5c17768f4 Add RemoveDuplicateDrops tool 2025-07-06 14:12:27 +02:00
P0nk
d08e1c4567 Remove drops from nonexistent mobs 2025-07-06 11:26:01 +02:00
P0nk
e7d94ee4fb Update skill & mastery books drop chances and fix max quantities 2025-07-06 11:16:36 +02:00
P0nk
89dfc37551 Refactor SkillbookChanceFetcher & add logs
Less static variables and generally cleaner code
2025-07-06 10:44:51 +02:00
P0nk
02f45397ce Remove duplicate mastery book drops 2025-07-05 21:48:25 +02:00
P0nk
82e377f39a Temp drop data table in separate changeset 2025-07-05 21:14:56 +02:00
P0nk
7227be3189 Remove Final Blow mastery book drop from unknown mob 2025-07-05 21:07:24 +02:00
P0nk
cad04a4725 Update drop chance that are 1500 (1/6666) to 1287 (1/7777) 2025-07-05 16:36:48 +02:00
P0nk
a2d6b88cde Update bow/crossbow arrow drop quantities 2025-07-05 16:30:23 +02:00
P0nk
81ab3e685d Remove duplicate crossbow arrow drops 2025-07-05 13:16:13 +02:00
P0nk
c43e833fbd Remove duplicate bow arrow drops 2025-07-05 10:54:43 +02:00
P0nk
3d39cce181 Clean up comments making the drop changeset fail 2025-07-05 10:45:33 +02:00
P0nk
9bbcf8c3ed Clean up empty lines 2025-07-05 10:15:44 +02:00
P0nk
669f214bd8 Update bronze xbow arrow drop quantities 2025-07-04 20:42:02 +02:00
P0nk
72c1a249e6 Update bronze bow arrow drop quantities 2025-07-04 20:32:41 +02:00
P0nk
f94925d1fe Remove duplicate bronze bow/xbow arrow drops 2025-07-04 20:18:03 +02:00
P0nk
9383608f68 Update steel bow arrow drop quantity 2025-07-04 20:06:44 +02:00
P0nk
2d7e82ceac Update red/blue arrow drop quantities 2025-07-04 20:06:24 +02:00
P0nk
7e4bbef888 Update diamond bow/xbow arrow drop quantities 2025-07-04 20:01:18 +02:00
P0nk
cd12d2bc9a Remove Snowball quantity update 2025-07-04 19:45:31 +02:00
P0nk
7ab52d0ccb Remove meso drop from mobs that shouldn't have them 2025-07-04 16:40:03 +02:00
P0nk
c85ac4f5d9 Add meso drops to mobs that are missing them 2025-07-04 16:37:40 +02:00
P0nk
1e4e284924 Copy Jr. Boogie 1 drops to Jr. Boogie 2 2025-07-04 15:25:20 +02:00
P0nk
a5a1de5362 Copy Fairy 1 drops to Fairy 2, Fairy 3 & Fairy 4 2025-07-04 15:23:06 +02:00
P0nk
76d8a4b985 Copy Dark Pepe drops to Separated Dark Pepe 2025-07-04 15:17:58 +02:00
P0nk
9f99aeacda Remove duplicate drops from Dark Pepe 2025-07-04 15:16:49 +02:00
P0nk
20399f5a87 Copy Pepe drops to Separated Pepe 2025-07-03 21:39:14 +02:00
P0nk
5e24a09ffe Remove duplicate Pepe drops 2025-07-03 21:37:50 +02:00
P0nk
11ed85abe0 Copy Dark Yeti drops to Transformed Dark Yeti 2025-07-03 21:30:46 +02:00
P0nk
dcd777faca Remove duplicate Dark Yeti drops 2025-07-03 21:29:19 +02:00
P0nk
c52d82c6f2 Copy drops from Yeti to Separated Yeti 2025-07-03 07:29:14 +02:00
P0nk
74aa5fc572 Copy drops from Yeti to Yeti (transformed) 2025-07-03 07:27:00 +02:00
P0nk
10882a09b0 Remove duplicate Yeti drops 2025-07-03 07:22:57 +02:00
P0nk
97c22055ac Remove unused content drops 2025-07-02 22:04:41 +02:00
P0nk
f38fb34c3a Copy drops from Orange Mushroom to Target Orange Mushroom 2025-07-02 14:54:14 +02:00
P0nk
871a47ad26 Remove duplicate Orange Mushroom drops 2025-07-02 14:52:36 +02:00
P0nk
c6823f5186 Applying drops for Dojo & various other mobs 2025-07-02 08:42:13 +02:00
P0nk
58e54c88af Fix Dark Nependeath drops 2025-07-02 08:39:43 +02:00
P0nk
994612d15f Remove non-card drops from Dojo mobs 2025-07-02 08:38:19 +02:00
P0nk
5ff522123a Remove Sword Earrings as a drop 2025-07-02 08:14:07 +02:00
P0nk
84f1989256 Remove Key of Dimension drop from Alishar copies 2025-07-02 08:13:10 +02:00
P0nk
cd0efcf2b6 Remove drops from pre-transformed Jr. Newtie 2025-07-02 08:11:27 +02:00
P0nk
1f5a38d9cf Remove drops from pq summoned mobs 2025-07-02 08:10:41 +02:00
P0nk
df83326a6f Remove drops from HPQ mobs 2025-07-02 08:02:14 +02:00
P0nk
49bc1c9b3d Remove Liar Tree Sap as drop 2025-07-02 07:59:49 +02:00
P0nk
6ce9d42ae4 Remove belt drops 2025-07-02 07:59:08 +02:00
P0nk
7e45cd6dab Improve chance of rare Masteria drops 2025-07-02 07:57:35 +02:00
P0nk
003efa9c8a Fix quest item drops 2025-07-02 07:47:59 +02:00
P0nk
2ab7b11b46 Adjust monster card drop chance for all boss mobs 2025-07-02 07:41:17 +02:00
P0nk
bdf1e52f70 Adjust monster card drop chance for all normal mobs 2025-07-02 07:37:06 +02:00
P0nk
636fb5fd53 Correct Dark Crystal drop chance 2025-07-02 06:51:10 +02:00
P0nk
fba0dbf8e3 Correct Flaming Feather drop chance 2025-07-02 06:49:22 +02:00
P0nk
f0be1bae4c Correct Halloween Candies drop chance 2025-07-02 06:47:59 +02:00
P0nk
c81c298dfe Correct monster cards & Blaze Capsule drop chance 2025-07-02 06:46:45 +02:00
P0nk
58644a1cd2 Correct AriantPQ drops 2025-07-02 06:42:22 +02:00
P0nk
7c2833e73b Correct CPQ drops 2025-07-02 06:36:43 +02:00
P0nk
8ca935753b Apply Giant Cake drops 2025-07-02 06:31:07 +02:00
P0nk
ab8ddc767f Correct Poisonous Mushroom drops 2025-07-02 06:27:26 +02:00
P0nk
7607e3d4b6 Correct Flaming Feather drops 2025-07-02 06:25:44 +02:00
P0nk
1504da67f6 Correct table for 2nd insert 2025-07-01 21:41:14 +02:00
P0nk
d6d8560d23 Consistent Nependeath Seed drop chance 2025-07-01 21:39:51 +02:00
P0nk
325f3b54a6 Fix Horned Mushroom 2 drop 2025-07-01 21:38:25 +02:00
P0nk
bfe174c242 Fix Sparta (lv 100) being dropped by lower lv mobs 2025-07-01 21:36:19 +02:00
P0nk
4747d88f40 Adjust zak and pb thematic drops 2025-07-01 21:13:15 +02:00
P0nk
af1878c3a0 Copy Reinforced Mithril Mutae drops to Obstacle Mutae 2025-07-01 21:09:58 +02:00
P0nk
a365d20fd8 Re-add drops for real(?) Pink Bean 2025-07-01 21:03:58 +02:00
P0nk
8c0350b16c Remove drops from Pink Bean clones 2025-07-01 21:01:50 +02:00
P0nk
c384d31827 Copy Pianus (R) drops to Pianus (L) 2025-07-01 20:57:45 +02:00
P0nk
8533d52c7a Add Bob the snail drops 2025-07-01 20:53:28 +02:00
P0nk
c08003a436 Clean up Freezer drops 2025-07-01 20:44:38 +02:00
P0nk
e9598d407b Apply unusual accessory scroll drops 2025-07-01 20:39:03 +02:00
P0nk
0e7c5358e2 Set minimum drop chance of skill/mastery books to 1/1000 2025-07-01 20:37:43 +02:00
P0nk
5da87f7b22 Add more skill/mastery books 2025-07-01 19:54:39 +02:00
P0nk
6e9ff7fb34 Remove drops from mobs that shouldn't have them 2025-07-01 17:41:47 +02:00
P0nk
7f149d56f0 Remove inexistent drop "[Mastery Book] Wind Booster" 2025-07-01 17:27:14 +02:00
P0nk
bfcadc0691 Apply drops that "should have been present by now" 2025-07-01 17:25:38 +02:00
P0nk
d6ad68d537 Apply horntail drops 2025-07-01 17:24:16 +02:00
P0nk
be98a7ab47 Apply kerning square drops 2025-07-01 17:21:33 +02:00
P0nk
469c70d1d7 Apply kerning square drop removals 2025-07-01 17:12:25 +02:00
P0nk
099275c5a3 Move "spider" drop data 2025-07-01 17:06:50 +02:00
P0nk
419f5d92b4 Allow duplicate drops for the same mob
This previously caused a bunch (327 to be exact) of drops to silently not be inserted due to "INSERT _IGNORE_ INTO drop_data (...)"
2025-07-01 17:01:40 +02:00
P0nk
05ca945145 Add drop data changeset
Not able to run it yet due to duplicate (dropperid, itemid) combos causing the constraint to reject the entire insert
2025-07-01 16:59:30 +02:00
P0nk
543cbafd0b Fix table name 2025-07-01 16:49:14 +02:00
P0nk
131379e39e Clean up tables in old script 2025-07-01 16:43:47 +02:00
P0nk
150754a74b Add global drop data changeset 2025-07-01 16:41:15 +02:00
P0nk
d4c65ed690 Add nxcoupons data changeset 2025-07-01 16:39:41 +02:00
P0nk
295144758d Add specialcashitems data changeset 2025-07-01 16:34:55 +02:00
P0nk
ad63aaad44 Let db generate shopitemid's 2025-07-01 16:31:58 +02:00
P0nk
b05e952268 Let db generate monstercarddata id's 2025-07-01 16:29:03 +02:00
P0nk
193257c25b Let db generate reactordropid's 2025-07-01 16:26:25 +02:00
P0nk
111c4f2677 Apply subsequent removals of reactor drop data 2025-07-01 16:22:31 +02:00
P0nk
84c5216b8f Add complementing reactor drop data 2025-07-01 12:58:49 +02:00
P0nk
0921396c57 Integrate quest id adjustments to reactor drop data 2025-07-01 12:51:06 +02:00
P0nk
8b76a28458 Add reactor drops data changeset 2025-07-01 12:42:55 +02:00
P0nk
fc0075831a Finalize monster card data
Exported the result of running the old queries from HeidiSQL and copy-pasted the INSERT.
2025-07-01 12:14:10 +02:00
P0nk
4f0182621a Add monster card data changeset (not complete) 2025-07-01 12:10:55 +02:00
P0nk
9e4ea4a474 Add maker reagent data changeset 2025-07-01 12:05:59 +02:00
P0nk
8ca2720ec1 Add maker reward data changeset 2025-07-01 12:03:47 +02:00
P0nk
c7137954fe Add maker recipe data changeset 2025-07-01 12:00:14 +02:00
P0nk
b410832a81 Add maker create data changeset 2025-07-01 11:55:16 +02:00
P0nk
da17490dbc Add shop data changesets 2025-07-01 09:06:42 +02:00
P0nk
371ed4efff Move duey to its own changeset 2025-07-01 08:25:30 +02:00
P0nk
e51dd82bdb Rename changesets to singular form 2025-07-01 08:15:37 +02:00
P0nk
66567fa595 Add bosslog changeset 2025-07-01 08:13:42 +02:00
P0nk
92db70159f Add ban changeset 2025-07-01 08:11:16 +02:00
P0nk
11cbf2a475 Add field objects changeset 2025-07-01 08:07:08 +02:00
P0nk
3367b58e39 Add maker changeset 2025-07-01 08:02:26 +02:00
P0nk
8f7068c55b Add mts changeset 2025-07-01 07:58:09 +02:00
P0nk
c28182c5a2 Add transfer changeset 2025-07-01 07:26:45 +02:00
P0nk
7bc946934a Add family changeset 2025-07-01 07:24:06 +02:00
P0nk
476ec0f0fe Add monsterbook changeset 2025-07-01 07:11:31 +02:00
P0nk
f8f67e61fa Add marriage changeset 2025-07-01 07:08:29 +02:00
P0nk
e9f24b4ae0 Add gift changeset 2025-07-01 07:05:28 +02:00
P0nk
503c76ed34 Add cash shop changeset 2025-06-30 22:09:59 +02:00
P0nk
307cb71023 Add character state changeset 2025-06-30 22:03:30 +02:00
P0nk
e6bfdbba6a Add missing foreign key (skill -> chr) 2025-06-30 22:02:23 +02:00
P0nk
9275802466 Add shops changeset 2025-06-30 17:22:59 +02:00
P0nk
ed01c6a54b Add more tables to quest changeset 2025-06-27 10:25:13 +02:00
P0nk
ddfac919cb Add storage changeset 2025-06-27 10:25:01 +02:00
P0nk
33e5ea5bdf Add drops changeset 2025-06-27 09:56:48 +02:00
P0nk
2559879aaa Add cooldowns to skill changeset 2025-06-27 09:50:08 +02:00
P0nk
630e56651b Add keymap changeset 2025-06-27 09:37:53 +02:00
P0nk
fb0ae8b080 Add guild changeset 2025-06-27 09:31:05 +02:00
P0nk
203f4bf34f Add quest changeset 2025-06-27 09:25:06 +02:00
P0nk
be1ba7229c Add pet changeset 2025-06-27 09:21:17 +02:00
P0nk
37e6b16656 Add skill changeset 2025-06-27 09:17:30 +02:00
P0nk
38233b39c2 Add inventory changeset 2025-06-27 09:12:09 +02:00
P0nk
d095179d6c Add characters changeset 2025-06-26 21:36:43 +02:00
P0nk
1e583e21cc Add accounts changeset 2025-06-26 21:29:51 +02:00
P0nk
75d5aad0b6 Use latest xsd 2025-06-26 21:08:46 +02:00
P0nk
b5eb5dc0b9 Liquibase POC 2025-06-26 15:35:26 +02:00
Ponk
431eced76a Merge pull request #307 from P0nk/chore/update-project #patch
Update project
2025-06-26 14:17:40 +02:00
P0nk
ac1f4c85b5 Refer to Cosmic-client for client setup 2025-06-26 14:11:57 +02:00
P0nk
ee8758e63b Upgrade dependencies 2025-06-26 13:53:40 +02:00
Lotus
76e114c9f2 [Handbook]: Add Skins, Jobs, Genders. Remove a few invalid "Pet" entries. (#285) #patch
* removed a few per entries that aren't in game, added Skin, Job, and Gender to handbook

* adjusted skin color + added to enum for db saves

* change skin names based on character creator's names for them.
2025-03-22 12:08:05 +01:00
Ponk
bb16165c51 Merge pull request #286 from v3921358/v3921358-npc-9120003-getmeso-check #patch
Fix incorrect method usage in NPC script (9120003.js)
2025-03-22 12:01:51 +01:00
Ponk
78e994402e Merge pull request #288 from P0nk/maintenance/maven-wrapper #patch
Add Maven Wrapper
2025-02-03 07:10:30 +01:00
P0nk
67ab800205 Add Maven Wrapper 2025-02-03 07:01:13 +01:00
v3921358
afb04b0d80 Fix incorrect method usage in NPC script (9120003.js)
Corrected the condition in NPC script 9120003.js by replacing cm.getMeso < price with cm.getMeso() < price to properly compare mesos against the price.
2024-12-12 19:55:48 +08:00
P0nk
e4430a3860 Merge branch 'master' into feat/postgresql-database 2024-10-21 05:52:25 +02:00
Ponk
a5b572023b Merge pull request #275 from nutnnut/fix-monster-crystal-level #patch
fix maker monster crystal level calculation
2024-10-19 13:52:08 +02:00
Ponk
380e4284ee Merge pull request #276 from JalenJaceX2/command-list #patch
Create Commands.txt
2024-10-19 13:44:27 +02:00
JalenJaceX2
16ff5a6f54 Create Commands.txt 2024-10-14 02:02:50 -07:00
Sirinut Euaungkanakul
cced80991f fix maker monster crystal level calculation
(cherry picked from commit 0f3611a41b449e195de6eceb686a97a40196b919)
2024-10-07 14:39:20 +07:00
P0nk
f2b8ced976 Move chr MySQL saving to CharacterSaver 2024-10-06 19:57:59 +02:00
P0nk
a4f8086da1 Log out everyone on startup 2024-10-03 21:36:19 +02:00
P0nk
e295a24d98 Unban macs & hwid 2024-10-03 21:36:04 +02:00
P0nk
64f99a29c6 Unban account & ip 2024-10-03 21:10:05 +02:00
P0nk
40425ac4e1 Ban ip, macs & hwid in PG
Finally rid of all db code in Client
2024-10-03 19:17:01 +02:00
P0nk
2b6ef9feb5 Save client addresses async on chr select
Almost rid of all db queries in Client
2024-10-03 08:28:55 +02:00
P0nk
e38344ceae Refactor BanService#ban 2024-10-02 06:54:55 +02:00
P0nk
5d81e05458 Redo mac bans - reduce amount of db queries on login
Works just like ip and hwid bans in that they are loaded on startup
2024-10-01 22:12:24 +02:00
P0nk
af02f8b744 Redo hwid bans - reduce amount of db queries on login
Works by loading all hwid bans on startup and querying the collection in memory
rather than making calls on every login.
2024-10-01 07:04:25 +02:00
P0nk
7661cd0f75 Redo ip bans - reduce amount of db queries on login
Works by loading all ip bans on startup and querying the collection in memory
rather than making calls on every login.
2024-09-30 19:31:21 +02:00
P0nk
167937bb88 Check banned hwid on login 2024-09-29 22:22:23 +02:00
P0nk
988d898d6f Clean up tempBanCalendar 2024-09-29 21:37:33 +02:00
P0nk
e35060da2a Handle ban solely through BanService 2024-09-29 20:25:11 +02:00
P0nk
c0ee1f8ffe Encapsulate account creation in AccountService 2024-09-29 19:33:20 +02:00
P0nk
99006c2dda Set ban through AccountService 2024-09-29 19:30:24 +02:00
P0nk
f142e21bbb Fix broken test 2024-09-29 18:14:05 +02:00
P0nk
1c6245fa6c birthday represented as LocalDate 2024-09-29 18:09:05 +02:00
P0nk
a307afae3c Unify ban handling on login 2024-09-29 17:55:51 +02:00
P0nk
50524a7740 Show correct ban reason 2024-09-29 17:30:09 +02:00
P0nk
d00b4ed678 Rename "temp_ban_timestamp" -> "temp_banned_until" 2024-09-29 16:34:04 +02:00
P0nk
b45620154c LoginState enum 2024-09-29 16:22:32 +02:00
P0nk
5450c29178 Reduce login state updates, fix multi-login on same acc 2024-09-29 15:55:25 +02:00
P0nk
fa666c98e6 Rename loginattempt 2024-09-29 14:24:56 +02:00
P0nk
902f1a154e Log out in PG on exit game
Can nog log in, enter game, exit game and re-login successfully.
2024-09-29 09:26:55 +02:00
P0nk
4e1aa1eb1a Rename NOT_LOGGED_IN -> LOGGED_OUT 2024-09-29 09:15:01 +02:00
P0nk
a580e44bc9 Rename AccountService methods 2024-09-29 09:14:32 +02:00
P0nk
da4a467453 Set in transition on log in, now able to enter the game
The state is not set properly on logout though, so once you log out you can't log back in
2024-09-29 08:22:26 +02:00
P0nk
0bb14e415e Log in through AccountService on player login 2024-09-29 07:52:42 +02:00
P0nk
0a8591aa08 Merge "serverTransition" and "inTransition" fields
They serve the same purpose, no point in separating
2024-09-29 07:43:47 +02:00
P0nk
813768ec47 Remove AddWorld,AddChannel,RemoveWorld,RemoveChannel cmds
These are incredibly error prone and hinder the database migration work.
2024-09-29 07:32:20 +02:00
P0nk
5595f5763b Log in through AccountService 2024-09-28 18:30:41 +02:00
P0nk
1be775394e Disable slow tests that don't provide much value 2024-09-28 18:30:31 +02:00
P0nk
55c9d4abbb Log out through AccountService 2024-09-28 18:26:23 +02:00
P0nk
93bbe868cb Use LoginState everywhere 2024-09-28 07:02:05 +02:00
P0nk
ec39f0fa06 Avoid additional db query to get login state during login 2024-09-28 06:55:45 +02:00
P0nk
439280947c Save gender to PG 2024-09-27 23:20:02 +02:00
P0nk
5abae50be5 Rework login, get account from PG 2024-09-27 22:52:12 +02:00
P0nk
082e0c0486 Save chr slots to PG 2024-09-27 17:59:26 +02:00
P0nk
2044166967 Save pic to PG 2024-09-27 17:34:36 +02:00
P0nk
f33d4fbc1c Save pin to PG 2024-09-27 06:50:07 +02:00
P0nk
0f2ef341ce Save accepted ToS to PG 2024-09-26 21:14:52 +02:00
P0nk
c7f835da0d Add DatabaseTest to easily test repositories 2024-09-26 19:51:18 +02:00
P0nk
647e67f6e8 Auto-create account in both MySQL and PG 2024-09-26 07:59:27 +02:00
P0nk
bf9c02bc16 Fix failed chr save due to null jail expiration 2024-09-26 07:59:11 +02:00
P0nk
1d5c26e67c Remove SHA-512 password migration 2024-09-26 06:48:13 +02:00
P0nk
b85233359f Save new chr to PG (doesn't work yet because no account) 2024-09-25 18:31:38 +02:00
P0nk
7335914695 Simplify sp saving 2024-09-25 18:07:50 +02:00
P0nk
b4e673baab Make some chr stats nullable to ease migration to PG 2024-09-25 17:48:12 +02:00
P0nk
767c4402e7 Route chr creation to dummy CharacterCreator 2024-09-25 07:21:27 +02:00
P0nk
a9967d53b1 Fix chr loading due to "monster_card" table not found 2024-09-25 06:58:19 +02:00
P0nk
00fc3ed1a1 Handle created chr with spec 2024-09-25 06:33:03 +02:00
P0nk
98d76ad45e Prepare centralized chr creation 2024-09-25 06:32:43 +02:00
P0nk
0c9643fd7e CharacterSaver integration test with Testcontainers 2024-09-15 11:41:03 +02:00
P0nk
ce5dee39ae Reorder migration scripts - move acc+chr creation earlier 2024-09-14 17:48:20 +02:00
P0nk
e57d2a9ee2 Create account, chr tables & save chr to Postgres 2024-09-14 17:44:48 +02:00
P0nk
f827e23ccc Clean up rewardpoints 2024-09-14 15:39:06 +02:00
P0nk
f8726640c3 Remove configurable language feature 2024-09-14 15:34:01 +02:00
P0nk
4778482cdd Remove quest point feature 2024-09-14 15:02:51 +02:00
P0nk
7297cd09b6 Separate PG from MySQL chr saving 2024-09-14 14:56:15 +02:00
P0nk
08eeeb54dc Rename ...Dao -> ...Repository 2024-09-14 12:06:09 +02:00
P0nk
abbec8120e Restructure chr saving - gather stats in record 2024-09-14 11:57:54 +02:00
P0nk
23a8a33e5f Move transaction handling to CharacterSaver 2024-09-13 23:59:29 +02:00
P0nk
758347f7bc Small refactor of disconnectInternal for readability 2024-09-13 23:23:35 +02:00
P0nk
1b6c0167de Fix buddy staying online in buddy list if dc in cash shop 2024-09-13 23:17:42 +02:00
P0nk
8bce0922d9 Fix broken global drops since merge from master 2024-09-13 23:02:12 +02:00
P0nk
f41268cdde Handle disconnect solely in TransitionService 2024-09-13 22:59:55 +02:00
P0nk
719b079cbc Refactor unnecessary wrapper boolean 2024-09-13 21:47:52 +02:00
P0nk
7bd5fa387d Merge branch 'refs/heads/master' into feat/postgresql-database 2024-09-13 21:29:50 +02:00
Ponk
5fabbaf7ab Merge pull request #261 from pimittens/vanillafixes #patch
chaos scroll fix
2024-09-13 21:25:51 +02:00
pimittens
215dc42294 Create RandomizerTest.java 2024-09-10 13:49:16 -07:00
P0nk
09a75e89c9 Merge branch 'refs/heads/master' into feat/postgresql-database
# Conflicts:
#	src/main/java/server/life/MonsterInformationProvider.java
#	src/main/java/server/maps/MapleMap.java
2024-09-03 17:45:27 +02:00
P0nk
559fe2d550 Merge branch 'refs/heads/master' into feat/postgresql-database
# Conflicts:
#	config.yaml
#	docker-compose.yml
#	pom.xml
#	src/main/java/client/Character.java
#	src/main/java/client/Client.java
#	src/main/java/client/MonsterBook.java
#	src/main/java/client/command/commands/gm0/BuyBackCommand.java
#	src/main/java/client/processor/stat/AssignAPProcessor.java
#	src/main/java/config/ServerConfig.java
#	src/main/java/net/server/channel/Channel.java
#	src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java
#	src/main/java/net/server/channel/handlers/BuddylistModifyHandler.java
#	src/main/java/net/server/channel/handlers/CloseRangeDamageHandler.java
#	src/main/java/net/server/channel/handlers/EnterMTSHandler.java
#	src/main/java/net/server/channel/handlers/NPCTalkHandler.java
#	src/main/java/net/server/channel/handlers/RangedAttackHandler.java
#	src/main/java/net/server/channel/handlers/SummonDamageHandler.java
#	src/main/java/net/server/channel/handlers/UseCashItemHandler.java
#	src/main/java/net/server/handlers/login/CreateCharHandler.java
#	src/main/java/net/server/world/World.java
#	src/main/java/scripting/npc/NPCConversationManager.java
#	src/main/java/server/ItemInformationProvider.java
#	src/main/java/server/life/Monster.java
#	src/main/java/server/life/MonsterInformationProvider.java
#	src/main/java/server/maps/MapleMap.java
#	src/main/java/tools/PacketCreator.java
#	src/test/java/service/NoteServiceTest.java
#	src/test/java/testutil/Any.java
2024-09-02 20:43:55 +02:00
Ponk
314916279a Merge pull request #271 from P0nk/fix/global-drop-concurrency #patch
Fix global drops thread issue
2024-08-28 21:30:39 +02:00
P0nk
dbf1a1bb36 Fix global drops thread issue
This randomly caused global drops to stop working after a while.
Originally reported by Crabo of MapleHorizons.
2024-08-28 21:22:47 +02:00
Ponk
8f2b8dd013 Merge pull request #248 from HarkuLi/fix/vard-plastic-surgery #patch
NPC (Vard): Fix "Plastic Surgery" option
2024-08-28 19:57:04 +02:00
Ponk
8039852aa3 Merge pull request #247 from NoirReverie/master #patch
Fix minimum HP and MP checks on AP reset
2024-08-28 18:30:15 +02:00
Ponk
d3b567953d Merge pull request #270 from P0nk/fix/exploded-meso-delay #patch
Fix staggered exploding mesos
2024-08-27 07:47:31 +02:00
P0nk
5064ee936a Fix staggered exploding mesos
Thanks teto: "the staggering should only go up to 400ms [attack.attackDelay + (index++ % 5) * 100], and the total delay should be capped at 1000ms according to the BMS implementation"
2024-08-27 07:41:24 +02:00
Ponk
86da9b0b29 Merge pull request #262 from P0nk/fix/drop-delay #patch
Proper delay for item drops
2024-08-20 20:38:27 +02:00
P0nk
acac203e42 Scheduler free timing for reactor drops 2024-08-20 20:32:33 +02:00
P0nk
cdd1c8cb61 Proper timing on removing exploded meso
No longer using scheduling on server side but rather
a delay value inherent to the "remove item from map" packet.
2024-08-20 20:13:01 +02:00
P0nk
439753eb6d Fix drop delay for Meso Explosion 2024-08-20 19:44:16 +02:00
P0nk
e30700de66 Fix drop delay from summon attack 2024-08-17 21:27:43 +02:00
P0nk
994d1723b6 Remove unnecessary "spawn loot on animation" feature
No longer needed since item drop timing is now dictated by the client
since two commits back.
2024-08-17 19:14:04 +02:00
P0nk
2d40a89c55 Overload damageMonster for when no delay is needed 2024-08-17 19:13:38 +02:00
P0nk
802cc2b5f5 Use delay from packets for drop timing 2024-08-17 19:13:36 +02:00
P0nk
2ffca90d29 Add attack delay to AttackInfo
Meant to be used in item drop packet for timing handled by the client
rather than scheduling item drop packets on the server.
2024-08-14 07:45:06 +02:00
pimittens
ec90df9c58 chaos scroll fix
The intention of the rand method is to return a pseudorandom uniformly distributed int between lbound and ubound (both inclusive). The previous implementation did not work as intended when the lower bound was negative since java round up instead of down when casting a negative double to an int. Adding the lower bound after the cast rather than before fixes this.

Also changed the CHSCROLL_STAT_RANGE from 6 to 5 to be consistent with gms.
2024-08-06 20:42:23 -07:00
Ponk
8f2c2dc08f Merge pull request #253 from P0nk/cleanup/rebirth #minor
Remove rebirth system
2024-07-19 17:24:18 +02:00
P0nk
cad10c4d5c Remove rebirth system 2024-07-19 17:16:14 +02:00
Ponk
aa3686ed0b Merge pull request #251 from P0nk/cleanup/banish #minor
Remove custom banish scroll feature
2024-07-17 19:47:15 +02:00
P0nk
3850b63cec Add migration for <v0.14.0 to fix shop dc 2024-07-17 19:24:19 +02:00
P0nk
a7df8a4f49 Remove banish scroll from WZ files 2024-07-17 18:50:44 +02:00
P0nk
7071b13e41 Remove banish scroll from shops 2024-07-17 18:39:50 +02:00
P0nk
2324ae7f9e Refactor BanishInfo - make it a record 2024-07-17 18:11:50 +02:00
P0nk
402163c33d Remove "banishable town scroll" feature 2024-07-17 17:45:38 +02:00
P0nk
3356e42e71 Remove "spikes avoid banish" feature 2024-07-16 20:17:21 +02:00
P0nk
205e263255 Specify required Java version 2024-07-16 20:16:26 +02:00
Ponk
aab9823a06 Merge pull request #227 from HarkuLi/feat/fix-ariant-coliseum #patch
Ariant Coliseum: Fix score counting and trade conversation
2024-06-19 17:58:38 +02:00
HarkuLi
0245e7d1af NPC (Vard): Fix "Plastic Surgery" option
Correct typos for variable `fface_v`.
2024-06-19 18:56:14 +08:00
HarkuLi
3f800c4a68 Ignore .DS_Store files 2024-06-19 18:43:26 +08:00
HarkuLi
31e901ab6d Ariant Coliseum: Fix score counting and trade conversation 2024-06-19 18:42:31 +08:00
Noir
94a08d86a0 Min HP / MP needs to check against post-AP-reset value 2024-06-18 21:42:52 -04:00
NoirReverie
bcc7bedbc9 Merge branch 'P0nk:master' into master 2024-06-17 18:53:21 -04:00
Noir
a878a4f3f9 Fix minimum HP and MP checks on AP reset 2024-06-17 18:48:13 -04:00
Ponk
238c01baf4 Merge pull request #245 from P0nk/fix/energy-charge-crash #patch
Fix Energy charge crashing certain other players
2024-06-16 19:13:03 +02:00
P0nk
5a4bdd343c Fix Energy charge crashing certain other players
Crabo in #bug-report (2024-06-10):
"(...) this will crash everyone in the map besides the bucc and 1 character, when a bucc (or TB) charges energy and a character with an ID that's a multiple of 102 is in the same map (and the energy reaches that number so if character ID is 204 it will reach that after 2 hits and DC the whole map besides the bucc and that char with id 204).
 Thanks to others for helping me fix it. Thought I'd report it!"
2024-06-16 19:09:21 +02:00
Ponk
d916502f58 Merge pull request #244 from P0nk/fix/cash-shop-surprise-package #patch
Fix able to get package from Cash shop surprise
2024-06-16 16:15:30 +02:00
P0nk
1791365e0f Fix able to get package from Cash shop surprise
Packages aren't real items and crash the client.
2024-06-16 16:06:29 +02:00
Ponk
de2a86c859 Merge pull request #243 from P0nk/fix/last-cash-shop-surprise #patch
Fix using the last Cash shop surprise not removing the item
2024-06-16 15:12:25 +02:00
P0nk
5aeed01e38 Fix not using the selected Cash shop surprise
If you have multiple, it would always use the first one. Now it uses whichever you select (as expected).
2024-06-16 15:08:48 +02:00
P0nk
6ab1af99da Add tests for CashShopSurpriseHandler 2024-06-16 13:56:54 +02:00
P0nk
c7b2d218ef Fix count not being updated after last Cash shop surprise 2024-06-16 12:59:30 +02:00
P0nk
01ae462b72 Refactor CashShop - add constants for cash types 2024-06-16 12:19:16 +02:00
Ponk
eb603e7ee9 Merge pull request #242 from P0nk/fix/cash-shop-surprise-count #patch
Fix cash shop surprise count not properly updated on usage
2024-06-15 08:35:58 +02:00
P0nk
b67b29def5 Fix cash shop surprise count not properly updated on usage
This reverts the hacky solution made in db82cbcfae
2024-06-15 08:31:24 +02:00
Ponk
d22d9b603b Merge pull request #228 from HarkuLi/feat/fix-npc-gain-meso #patch
NPC: Fix type casting error for `gainMeso()` method
2024-06-14 21:44:10 +02:00
Ponk
16b0a36c86 Merge pull request #238 from channarit1994/master #patch
Surprise Box Implementation
2024-06-14 21:33:08 +02:00
Ponk
313f48d4ce Merge pull request #237 from peamy/master #patch
Check if the amount of damage lines doesn't exceed the max (autoban)
2024-06-14 20:11:39 +02:00
Channarit Sittiparat
db82cbcfae Surprise Box Implementation
- Added showCashInventory after a successful gachapon opening in the CashShopSurpriseHandler.
- Added getItemsSize() condition to check the inventory size before proceeding with the cash shop surprise opening
2024-06-13 20:02:40 +07:00
remsus
eed47a9064 Check if the amount of damage lines doesn't exceed the max (autoban) 2024-06-11 18:44:47 +02:00
Ponk
9945d37df8 Merge pull request #233 from P0nk/fix/ide-settings #patch
Add IntelliJ code style settings (no wildcard imports)
2024-05-22 08:51:47 +02:00
P0nk
4e743128e9 Remove all wildcard imports 2024-05-22 08:33:44 +02:00
P0nk
2ed35db216 Add IntelliJ code style settings (no wildcard imports) 2024-05-22 08:06:46 +02:00
Ponk
df0c7f8b46 Merge pull request #232 from P0nk/cleanup/remove-buyback #minor
Remove custom sounds in Sound.wz, add devtest command
2024-05-21 22:54:59 +02:00
P0nk
9fe6ba4483 Add new wz files zip (reduced size by ~400mb)
Removed: Base.wz, Effect.wz, Morph.wz, Sound.wz, TamingMob.wz
2024-05-21 22:47:47 +02:00
P0nk
dee8651e61 Add DevtestCommand to easily test without restarting server 2024-05-21 21:36:54 +02:00
P0nk
948a9de667 Remove Spirit of Rock die sound 2024-05-21 20:23:34 +02:00
P0nk
d55437ddf3 Remove subway whistle 2024-05-21 19:56:09 +02:00
P0nk
98fd8c13d4 Remove brazil anthem 2024-05-21 19:54:30 +02:00
P0nk
04a92fe0c1 Remove buyback custom feature 2024-05-21 19:44:25 +02:00
Ponk
5754b60ca0 Merge pull request #231 from P0nk/feat/project-update #minor
Upgrade Java version and dependencies
2024-05-10 22:29:51 +02:00
P0nk
dc1a712f52 Re-add section about running from jar 2024-05-10 22:26:04 +02:00
P0nk
7a6d3e1b68 Add feature request issue template 2024-05-10 21:29:09 +02:00
P0nk
970fb3155f Complement pom.xml 2024-05-09 22:29:15 +02:00
P0nk
0eb78e2d9e Use root db user by default 2024-05-09 22:08:28 +02:00
P0nk
7d42870abd Fix sql script not being runnable on newer versions of MySQL 2024-05-09 21:26:09 +02:00
P0nk
c7b9e0eee3 Major rewrite of README 2024-05-09 21:11:53 +02:00
P0nk
c5089881b3 Tidy pom
mvn tidy:pom
2024-05-09 14:24:03 +02:00
P0nk
9c1406f75d Update pull request template 2024-05-09 12:33:50 +02:00
P0nk
92226483be Merge branch 'refs/heads/master' into feat/project-update 2024-05-09 12:30:14 +02:00
Ponk
21ecfeef17 Add pull request template 2024-05-09 12:20:08 +02:00
Ponk
d5be9130aa Add bug report issue template 2024-05-09 11:53:54 +02:00
P0nk
f61fee829a Upgrade Java version and dependencies 2024-05-09 11:45:16 +02:00
HarkuLi
0d684c1400 NPC: Fix type casting error for gainMeso() method
Number type values might be passed into the `gainMeso()` method in js
scripts, and thus it expects a `gainMeso(Double gain)` method in the
`NPCConversationManager` class.
2024-03-02 15:38:22 +08:00
Ponk
b5871c7be2 Merge pull request #219 from Silwhoon/mobskill-prop-handling #patch
Mob Skills now have a success rate
2024-02-07 06:49:24 +01:00
Silwhoon
f9b328b432 Mob Skills now have a success rate
This was only used for MAGIC_IMMUNITY and WEAPON_IMMUNITY for some reason? A lot of other MobSkills also have a 'prop' node in the WZ files such as; Darkness and Seduce
2024-02-05 17:58:43 +00:00
Ponk
830df4e5ca Merge pull request #218 from Silwhoon/makecharinfo #minor
Utilize the MakeCharInfo.img data in the WZ files
2024-02-05 18:09:12 +01:00
Silwhoon
6f68f4edfd Addressed nitpicks and removed unused item ID constants 2024-02-05 17:00:18 +00:00
Silwhoon
a7931c3e4d New characters now utilise the MakeCharInfo.img data in the WZ files 2024-02-05 16:03:41 +00:00
Ponk
799cb97564 Merge pull request #217 from P0nk/fix/210-clean-slate-scroll #minor
Fix able to gain infinite slots with clean slate scroll
2024-02-05 08:39:25 +01:00
P0nk
851b57e8ef Fix able to gain infinite slots with clean slate scroll
And remove a GM config option.
Thanks Crabo for the suggested solution.
2024-02-05 08:34:36 +01:00
P0nk
a7d03cf8c0 Merge branch 'master' into feat/postgresql-database 2024-02-04 15:12:14 +01:00
Ponk
ceb2866aa1 Merge pull request #214 from leevccc/master #patch
fix: item lock cant extend lock time
2024-02-04 14:22:00 +01:00
Ponk
11c1e4655e Merge pull request #213 from yuzumika/ppq-npc-fix #patch
Fix bug in PPQ where non-party leader can get stuck in an NPC dialogue loop
2024-02-04 12:09:33 +01:00
Ponk
aca9cbf91d Merge pull request #212 from yuzumika/fix-selection-underflow #minor
NPCMoreTalkHandler: don't underflow selection
2024-02-04 12:07:46 +01:00
leevccc
08b089d9be fix: seal lock cant extend lock time 2024-01-14 22:13:16 +08:00
leevccc
8b254a294e fix: item lock cant extend lock time 2024-01-14 18:15:48 +08:00
yuzumika
7004de6e71 ppq: fix bug where non-party leader can get stuck in an npc dialogue loop 2024-01-04 14:30:59 -08:00
yuzumika
738e1b24e6 add unit tests for ByteBufInPacketTest.readUnsignedByte 2024-01-04 13:42:20 -08:00
yuzumika
5a4200cc8e implement ByteBufInPacket.readUnsignedByte 2024-01-04 10:55:07 -08:00
yuzumika
cb0320a471 NPCMoreTalkHandler: don't underflow selection 2024-01-03 16:10:23 -08:00
Ponk
058f034c2b Merge pull request #209 from yuzumika/minigame-npc-dispose #patch
Minigame NPC (Casey) tweaks
2024-01-03 20:13:59 +01:00
yuzumika
9c54f3a8ea minigame npc: add more disposes, fix typo 2023-12-27 15:35:45 -08:00
yuzumika
ee8cb545e1 minigame npc: use omokamount constant consistently; tiny text tweak to
make it more GMS-like
2023-12-27 15:18:22 -08:00
yuzumika
64bbff462d minigame npc: dispose after creating a set of match cards 2023-12-27 14:54:10 -08:00
Ponk
f63f7e13d4 Merge pull request #205 from yuzumika/face-hair-dedup #patch
use isFace and isHair in ItemInformationProvider.getStringData
2023-12-10 09:49:55 +01:00
yuzumika
db8666fc71 use isFace and isHair in ItemInformationProvider.getStringData 2023-11-24 01:38:34 -08:00
Ponk
93ea66e6fe Merge pull request #202 from peamy/master #patch
Bugfix: unable to complete scipted quests remotely
2023-11-10 21:11:37 +01:00
Ice Bear
7131e39c96 Merge pull request #1 from P0nk/master
Pull cosmic master into Peamy master
2023-11-10 11:40:55 +01:00
remsus
b80e9a3310 Bugfix: unable to complete scipted quests remotely 2023-11-10 00:20:52 +01:00
P0nk
033d91ed71 Merge branch 'master' into feat/postgresql-database
# Conflicts:
#	config.yaml
#	src/main/java/config/ServerConfig.java
2023-11-08 21:21:07 +01:00
Ponk
03802666ef Merge pull request #190 from ormizj/character-exp-log #minor
added logs for character exp gain
2023-11-08 21:16:57 +01:00
Ponk
ecd155f2bb Merge pull request #201 from peamy/master #patch
BugFix: can't give CASH items to player (prepareInventoryItemList)
2023-11-08 20:50:50 +01:00
remsus
d6147d5191 Fix preparation of invList (CASH was skipped) in prepareInventoryItemList (AbstractPlayerInteraction) 2023-11-08 11:26:55 +01:00
ormizj
c145a53688 fixed wrong method call 2023-09-15 01:04:22 +03:00
spiderpig60
c744935dd0 changed starting server to vanilla values 2023-09-10 21:48:02 +03:00
spiderpig60
90b44c3a8b added timed thread, added batch, cleaned code
changed starting value to false
2023-09-10 21:36:52 +03:00
spiderpig60
60a44252ea added logs for character exp gain 2023-09-10 01:53:53 +03:00
P0nk
392a350eab Merge branch 'master' into feat/postgresql-database
# Conflicts:
#	docker-compose.yml
2023-08-20 18:30:52 +02:00
P0nk
f33df59f49 Move some Client#forceDisconnect calls to TransitionService 2023-08-11 22:00:33 +02:00
P0nk
cd75e85bec Move some autoban logic to new BanService 2023-08-10 22:52:09 +02:00
P0nk
cb31121fe7 Disconnect client with TransitionService 2023-08-10 21:58:55 +02:00
P0nk
d5682a5f65 Add client disconnection logic to TransitionService
Problem: disconnecting requires access to CharacterSaver,
which is not available in Client.
Having it in a service like this solves that problem.

Next step is to migrate all calls to Client#disconnect and Client#forceDisconnect
to their TransitionService counterparts.
2023-08-08 21:51:12 +02:00
P0nk
f6d06ba82a Extract "char list" from Client to handler 2023-08-06 21:14:32 +02:00
P0nk
bbee8d7caa Merge chr name + id wrappers into new CharacterIdentity record 2023-08-06 20:35:26 +02:00
P0nk
5b5888cf65 Remove unused stuff in Client 2023-08-06 20:11:32 +02:00
P0nk
48d9aaa871 Clean up Client - visibleWorlds & canRequestCharlist
canRequestCharlist is a relic from the past when "View all char"
functionality was hacked together with wrong packets.

visibleWorlds I'm less sure about. I suppose it's useful if you add world
(via command) while someone is still on the login screen.
But the functionality of adding/removing worlds live is a recipe for disaster
and will eventually (likely) be removed.
2023-08-06 20:02:39 +02:00
P0nk
f44083aeba Refactor Client#finishLogin 2023-08-06 17:26:34 +02:00
P0nk
810dbcc1d7 Remove "reward points" feature 2023-08-06 16:55:58 +02:00
P0nk
449ab01bc2 Remove "vote points" feature 2023-08-06 16:55:34 +02:00
P0nk
2686b2b02d Disconnect client by throwing exception in handler
This makes it easier to add checks in handlers, which should improve security over time.
I think this approach is more readable and testable than calling Client#disconnect straight up,
while it also decentralizes the handling.
2023-08-06 15:48:49 +02:00
P0nk
e9819fac87 Save cooldowns and diseases as part of normal flow 2023-08-04 16:34:59 +02:00
P0nk
4e39142fb3 Direct almost all chr saving through CharacterSaver
Client#disconnectInternal remains.
Had to remove some configurable save points to keep it simple.
2023-08-04 16:14:51 +02:00
P0nk
e52f646558 Fix batch insert monster cards
batch.add() clears all bindings, unlike vanilla JDBC
2023-08-04 15:38:14 +02:00
P0nk
02d4ff524a Fix global drops not dropping
No longer able to shuffle the list since DropProvider
return an unmodifiable list in the case of global drops.
2023-08-04 15:23:02 +02:00
P0nk
05b7ec77c8 Add ChannelService to handle cc'ing 2023-08-04 15:01:49 +02:00
P0nk
f6aa8ceba6 Rewrite MonsterBook, touch up chr loading
Temporarily disabled loading monster cards from db
2023-07-25 21:27:35 +02:00
P0nk
b3ec325e95 Add monster card table and initial dao 2023-07-25 18:46:03 +02:00
P0nk
e31465a1b5 Add MonsterCard 2023-07-25 17:52:38 +02:00
P0nk
84ad5b4db8 Remove scroll generator (custom feature)
This allowed me to remove the getCardTierSize db query
2023-07-25 17:20:16 +02:00
P0nk
855b66c459 Merge branch 'master' into feat/postgresql-database 2023-07-25 17:03:35 +02:00
P0nk
46f767d79c Merge branch 'master' into feat/postgresql-database 2023-05-29 15:56:53 +02:00
P0nk
785f74ed21 Fix HelpCommand not working without static CommandsExecutor 2023-03-31 07:59:43 +02:00
P0nk
699da37f06 Simplify NPC script start 2023-03-31 07:33:49 +02:00
P0nk
c3badba73b Convert ShopItem to record 2023-03-30 07:07:33 +02:00
P0nk
5f0e9a355b Move Shop stuff to own package 2023-03-30 06:47:07 +02:00
P0nk
39d759595d Get shop as Optional 2023-03-30 06:41:13 +02:00
P0nk
fe9dd75a23 Instantiate shops in ShopFactory 2023-03-29 21:59:00 +02:00
P0nk
2139147dcd Add getShop to DAO, preparing to refactor shop loading 2023-03-29 21:14:19 +02:00
P0nk
c71ca7f4d5 Inject ShopFactory 2023-03-29 20:51:31 +02:00
P0nk
705efb4340 Remove access to shops from scripts 2023-03-29 20:38:34 +02:00
P0nk
ec1450cec1 Rewrite Mo NPC script to be GMS-like 2023-03-29 19:54:59 +02:00
P0nk
290bf78db3 Notes about difficulties of DI for scripts 2023-03-18 16:04:01 +01:00
P0nk
02c1fe46f3 Remove custom shops
These custom shops we introduced in HeavenMS.
2082014 - Asia selling scrolls
9201101 - T-1337 selling special potions
2023-03-18 15:14:00 +01:00
P0nk
7315dd80fc Remove redundant scripts for Ariant weapon & armor merchants 2023-03-18 15:08:06 +01:00
P0nk
a496c0f2b4 Add ShopDao 2023-03-18 14:32:21 +01:00
P0nk
ab0239ba84 Add shop and shop_item PG tables with data 2023-03-17 20:53:16 +01:00
P0nk
f2f3abdb32 Move last pieces of monster drops to DropProvider 2023-03-16 18:55:33 +01:00
P0nk
5a35b55d7a Cache continent drops 2023-03-16 18:03:43 +01:00
P0nk
02aa4a237f Global monster drops are retrieved from DropProvider (postgres db) 2023-03-16 18:00:21 +01:00
P0nk
cc88d382e6 Monster drops are retrieved from DropProvider (postgres db)
This commit emphasizes the need for events to be reworked.
This is the chain of constructors the DropProvider has to pass through to reach MapleMap:
Channel -> EventScriptManager -> EventManager -> EventInstanceManager -> MapManager -> MapleMap
2023-03-16 08:31:38 +01:00
P0nk
a95fa2efc1 Refactor LootInventory & LootManager 2023-03-16 00:25:21 +01:00
P0nk
45e2b93724 Remove "multiple same equip drop" feature 2023-03-15 23:26:15 +01:00
P0nk
fa3481fa99 Pass CommandContext to commands
CommandContext is the carrier of dependencies.
Currently, it only carries a DropProvider, but
it will grow bit by bit as more static singletons
and other similar structures get refactored.
2023-03-15 23:19:04 +01:00
P0nk
eed94ec34a Refactor CommandsExecutor - is no longer static singleton
Preparing for change in Command#handle,
which is going to take a CommandContext as an additional argument.
This way we can pass in command dependencies in a safe way
instead of requiring them to access static methods.
2023-03-15 22:56:40 +01:00
P0nk
a6a7a26ebc Clean up Steal stuff in MIP 2023-03-15 22:41:36 +01:00
P0nk
7ed7b25268 Fix race condition when concurrently Stealing 2023-03-15 22:38:54 +01:00
P0nk
703ae30a27 Move "steal item" logic to DropProvider 2023-03-15 22:22:14 +01:00
P0nk
6bf8785f22 Fix error in monster_drop query 2023-03-15 22:17:42 +01:00
P0nk
53768555a2 Add table, data, and provider method for global drops 2023-03-09 20:08:20 +01:00
P0nk
a70f4e303d Add DropProvider and dao for monster drops 2023-03-08 22:46:24 +01:00
P0nk
7a848cf6a1 Add monster_drop table with data 2023-03-08 22:31:14 +01:00
P0nk
0be48568d7 Get MakerItemCreateEntry from new info provider 2023-03-08 21:09:47 +01:00
P0nk
1fd0963401 Fix maker disassembly fee not being applied 2023-03-04 22:53:05 +01:00
P0nk
d2d4b442d2 Maker disassembly info to MakerInfoProvider
MakerProcessor is such a mess...
2023-03-04 22:33:34 +01:00
P0nk
9d6574d3ba Cache maker info with Caffeine 2023-03-04 21:27:18 +01:00
P0nk
f2ca67aba4 Add tests for MakerInfoProvider
These are going to be handy soon,
as I'm about to add Caffeine for caching.
2023-03-04 21:02:04 +01:00
P0nk
6073b20d65 Cache MakerRecipe, move stimulant to MakerInfoProvider 2023-03-04 17:20:16 +01:00
P0nk
b329709776 Expose containerized databases to host
This way you can connect with your favorite db client
2023-03-04 16:04:18 +01:00
P0nk
932f9fc443 Merge branch 'master' into feat/postgresql-database
# Conflicts:
#	pom.xml
2023-03-02 18:40:27 +01:00
P0nk
c0c0a2d2d9 Get maker reagent from PG db, rework processor (no statics) 2023-03-02 08:18:13 +01:00
P0nk
5cecb7adb6 Add maker_random_reward table and data (formerly "makerrewarddata") 2023-03-01 21:09:32 +01:00
P0nk
26357a8d7b Add maker_reagent table and data (formerly "makerreagentdata") 2023-03-01 20:51:26 +01:00
P0nk
9819fd5bb1 Add maker_recipe_ingredient table and data (formerly "makerrecipedata") 2023-03-01 20:45:51 +01:00
P0nk
adbdb89917 Add maker_recipe table and data (formerly "makercreatedata") 2023-03-01 20:34:48 +01:00
P0nk
b7c79800eb Fix note not showing if case mismatch in name
This wouldn't be a problem in the first case,
if there was a foreign key connection between
note and character tables.
Can't add that quite yet though.
2023-02-28 21:30:20 +01:00
P0nk
55e1f65bbd Fix server not able to use note sequence 2023-02-27 19:55:01 +01:00
P0nk
d018b0f5e5 Add note receiver index
Only makes a difference if there is
a large amount of notes in the database.
Better be safe than sorry.
2023-02-27 18:16:23 +01:00
P0nk
12745c207d Move notes to the Postgres database 2023-02-25 00:40:36 +01:00
P0nk
f6f3c9c3e3 Initiate Postgres connection pool on startup 2023-02-25 00:11:33 +01:00
P0nk
8bb825ef02 Add Docker compose support for Postgres 2023-02-24 23:42:13 +01:00
P0nk
f1192279bf Add Flyway, run db migration in local PostgreSQL db
First step in my plan to switch away from MySQL.
2023-02-24 21:49:00 +01:00
588 changed files with 72784 additions and 56053 deletions

23
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,23 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.

View File

@@ -0,0 +1,15 @@
---
name: Feature request
about: Suggest an idea for this project, such as a missing gameplay feature
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.

11
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,11 @@
## Description
<!-- Describe your changes in detail -->
## Checklist before requesting a review
<!-- Mark with "x" inside the square brackets -->
- [ ] I have performed a self-review of my code
- [ ] I have tested my changes
- [ ] I have added unit tests that prove my changes work
## Screenshots
<!-- If applicable, add screenshots to help explain your changes -->

View File

@@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
java-version: '21'
distribution: 'corretto'
- name: Build with Maven (compile -> test -> package)
run: mvn -B package --file pom.xml

10
.gitignore vendored
View File

@@ -1,5 +1,6 @@
/logs/**
.idea/
.idea/*
!.idea/codeStyles/
*.iml
/target
@@ -16,3 +17,10 @@
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Database
database/docker-db-data
database/docker-pg-db-data
# macOS files
.DS_Store

8
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
</JavaCodeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

19
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

View File

@@ -4,15 +4,21 @@
#
# Cosmic JAR creation stage
#
FROM maven:3.9.1-eclipse-temurin-17 AS jar
FROM maven:3.9.6-amazoncorretto-21 AS jar
# Build in a separated location which won't have permissions issues.
WORKDIR /opt/cosmic
# Any changes to the pom will affect the entire build, so it should be copied first.
COPY pom.xml ./pom.xml
# Grab all the dependencies listed in the pom early, since it prevents changes to source code from requiring a complete re-download.
# Skip compiling tests since we don't want all the dependecies to be downloaded.
RUN mvn -f ./pom.xml clean dependency:go-offline -Dmaven.test.skip -T 1C
# Skip compiling tests since we don't want all the dependencies to be downloaded.
# RUN mvn -f ./pom.xml clean dependency:go-offline -Dmaven.test.skip -T 1C
# TODO: The above command stopped working as of Java 21 upgrade due to:
# Failed to execute goal org.apache.maven.plugins:maven-dependency-plugin:3.6.1:go-offline (default-cli) on project Cosmic: org.eclipse.aether.resolution.DependencyResolutionException: The following artifacts could
# not be resolved: io.netty:netty-tcnative:jar:${os.detected.classifier}:2.0.65.Final (absent): Could not find artifact io.netty:netty-tcnative:jar:${os.detected.classifier}:2.0.65.Final in central (https://repo.maven.apache.org/maven2) -> [Help 1]
# Source code changes may not change dependencies, so it can go last.
# Skip compiling tests since we don't want all the dependecies to be downloaded for plugins.
COPY src ./src
@@ -21,7 +27,7 @@ RUN mvn -f ./pom.xml clean package -Dmaven.test.skip -T 1C
#
# Server creation stage
#
FROM eclipse-temurin:17.0.6_10-jre
FROM amazoncorretto:21
# Host the server in a location that won't have permissions issues.
WORKDIR /opt/server

350
README.md
View File

@@ -1,303 +1,155 @@
# Cosmic - MapleStory v83 server
# Cosmic
Cosmic is a server emulator for Global MapleStory (GMS) version 83.
## Introduction
Cosmic launched as a successor to HeavenMS on March 21st 2021.
HeavenMS is archived, ie. it receives no further updates. This project aims to continue its development; mainly by improving code quality and make getting into PS development as easy as possible.
Cosmic launched on March 2021. It is based on code from a long line of server emulators spanning over a decade - starting with OdinMS (2008) and ending with HeavenMS (2019).
This is an open source project. Anyone may contribute by opening a pull request.
This is mainly a Java based project, but there are also a bunch of scripts written in JavaScript.
Only the server side is maintained. The client is directly copied from HeavenMS.
Head developer and maintainer: __Ponk__.\
Contributors: a lot of people over the years, and hopefully more to come. Big thanks to everyone who has contributed so far!
Join the Discord server where most of the discussions take place: https://discord.gg/JU5aQapVZK
Beware - ***This server emulator is not production ready.***
It can be useful for testing things locally or for trying out ideas, but launching a new private server based on this and opening it up to the public
without knowing what you're doing is not recommended.
### Goals
What we are working towards.
* __Vanilla gameplay__ - stay as close to the original game as possible (within reason).
* __Ease of use__ - getting started should be frictionless and contributing to the project straightforward.
* __Reduce technical debt__ - making changes should be easy without causing unintended side effects.
* __Modern tools & technologies__ - stay appealing by continuously improving the code and the project as a whole.
### Non-goals
Explicitly excluded from the scope of the project.
* __Custom gameplay features__ - existing custom features will be removed over time and new ones are unlikely to be added.
* __Client development__ - this project is focused on the server. Please go elsewhere for client related questions.
* __Public server__ - there will not be an official Cosmic server open to the public. Feel free to launch your own server __at your own risk__. No support will be provided.
## Development information
## Project setup
### Status (updated 2022-10-16)
### Contribute
You may contribute to the project in various ways, mainly through GitHub:
* Providing improvements to the code through a [Pull Request](https://github.com/P0nk/Cosmic/pulls) from your own fork.
* Reporting a bug by creating an [Issue](https://github.com/P0nk/Cosmic/issues).
* Providing information to existing issues or reviewing pull requests that others have made.
* ...and in other ways that I haven't thought of!
Development is currently <span style="color:Yellow">**sporadic**</span>.
### Continuous integration
A GitHub Actions pipeline is set up to run the build automatically when a new pull request is opened or commits are pushed to an existing one. This ensures that the code compiles and all the tests pass.
My time is very limited nowadays, but I try to keep up with the submitted pull requests. I may submit some stuff of my own, once in a while.
Once a pull request is merged, a tag with the new version is automatically created.
### Ways to contribute
* Submit a Pull Request (fork -> commit -> PR). If you don't know where to start, have a look at the issues on GitHub.
* Report a bug (preferably as an Issue on GitHub, as reports on Discord may be forgotten or lost)
* Spread the word about Cosmic
### Working with GitHub
Anyone with a GitHub account can contribute by making some changes in a branch and opening up a PR.
All activity on the GitHub repo (opening PR, commenting, creating issue, etc.) is automatically pushed (via webhook) to a public Discord channel for visibility.
Issues is the main place where bugs, issues or general improvements are tracked. Feel free to submit a new issue, but please keep it in English. By providing a good description, you increase the chance of a bug being fixed.
Tasks (past, present and future) are kept in the Cosmic project, which you get to via the "Projects" tab. This gives you an idea of where the project is moving.
### Discord integration
Most GitHub activity is pushed to a Discord channel for visibility. This works by leveraging a webhook. The activity includes (but is not limited to): merged commits, created PRs, comments, and new tags.
### Versioning
The project follows the [SemVer](https://semver.org/) versioning scheme using git tags.
As a pull request gets merged, a new version is automatically created.
Bug fixes result in bumped patch version: 1.2.__3__ -> 1.2.__4__
General improvements result in bumped minor version: 1.__2__.3 -> 1.__3__.3
Major changes result in bumped major version: __1__.2.3 -> __2__.2.3
### Cosmic
- GitHub: https://github.com/P0nk/Cosmic
- Discord: https://discord.gg/JU5aQapVZK
### HeavenMS
- GitHub: https://github.com/ronancpl/HeavenMS
- Discord: https://discord.gg/Q7wKxHX
## Tools / downloads
* **Java 17 SDK** - Needed to compile and run Java code. Install manually or through IntelliJ depending on how you prefer to launch the server. Not required for launching with Docker.
* Link: https://jdk.java.net/17/
* **IntelliJ IDEA** - Java IDE and your main tool for working with the source code. Community edition is good enough.
* Link: https://www.jetbrains.com/idea/
* **MySQL Community Server 8** - Database for game data.
* Link: https://dev.mysql.com/downloads/mysql/
* **MySQL Workbench 8** - Client for interacting with the database. Other clients do exist.
* Link: https://dev.mysql.com/downloads/workbench/
* **Docker Desktop** (optional) - For launching the game locally with less hassle.
* Link: https://www.docker.com/products/docker-desktop
* **Client files and general tools**
* Link: https://drive.google.com/drive/folders/1hgnb92MGL6xqEp9szEMBh0K9pSJcJ6IT?usp=sharing
* This is Ponk's own Google Drive, similar to how Ronan provides files for HeavenMS.
### MapleStory client
- Latest localhost client: https://hostr.co/amuX5SLeeVZx
**Important note about localhost clients**: these executables are red-flagged by antivirus tools as __potentially malicious software__,
this happens due to the reverse engineering methods that were applied onto these software artifacts.
Those depicted here have been put to use for years already and posed no harm so far, so they are soundly assumed to be safe.
The project follows the [semantic versioning](https://semver.org/) scheme using git tags.
* *Bug fixes* are treated as PATCH: 1.2.__3__ -> 1.2.__4__
* *General changes or improvements* are treated as MINOR: 1.__2__.3 -> 1.__3.0__
* *Major changes* are treated as MAJOR: __1__.2.3 -> __2.0.0__
## Getting started
The localhost MapleStory client needs to be installed, as well as the server that will host the game.
Follow along as I go through the steps to play the game on your local computer from start to finish. I won't go into extreme detail, so if you don't have prior experience with Java or git, you might struggle.
### Installing the client
We will set up the following:
- Database - the database is used by the server to store game data such as accounts, characters and inventory items.
- Server - the server is the "brain" and routes network traffic between the clients.
- Client - the client is the application used to _play the game_, i.e. MapleStory.exe.
1. Install MapleStory with "MapleGlobal-v83-setup.exe" in your folder of choice (e.g. "C:\Nexon\MapleStory") and follow their instructions.
2. Once done, erase these files: "HShield" (folder), "ASPLnchr.exe", "MapleStory.exe" and "Patcher.exe".
3. Extract into the client folder the "HeavenMS-localhost-WINDOW.exe" (from now on referred to as "localhost.exe") from the provided link.
4. Overwrite the original WZ files with the ones provided on the Google Drive: "CosmicWZ-v1-2021.05.10.zip"
- This is currently identical to the latest HeavenMS WZ files (except for the file name): "commit397_wz-20210321T173600Z-001.zip"
### 1 - Database
You will start by installing the database server and database client. Then you will connect to the server with the client to create a new database schema.
#### Editing localhost IP target
#### Steps
If you are not using "localhost" as the target IP on the server's config file, you will need to HEX-EDIT localhost.exe to fetch your IP. Track down all IP locations by searching for "Type: String" "127.0.0.1", and applying the changes wherever it fits.
1. Download and install [MySQL Community Server 8+](https://dev.mysql.com/downloads/mysql/). You will have to set a root password. Make sure you don't lose it because you will need it later.
2. Download and install [HeidiSQL](https://www.heidisql.com/download.php).
3. Connect to the database:
1. Open HeidiSQL
2. Create a new Session: "New" -> fill in your password -> "Save"
3. Connect to the database: click on your saved session -> "Open"
4. Create a new database schema:
1. In the opened session, right-click on the session name in the menu on the left
2. "Create new" -> "Database" -> database name should be "cosmic" -> "OK"
5. Done. The database is now ready. Once the Cosmic server starts, it will create tables and populate some of them with initial data.
To hex-edit, install the Neo Hex Editor from "free-hex-editor-neo.exe" and follow their instructions. Once done, open localhost.exe for editing and overwrite the IP values under the 3 addresses. Save the changes and exit the editor.
### 2 - Server
You will start by cloning the repository, then configure the database properties and lastly start the server.
(TODO: find suitable alternative to Neo Hex Editor)
#### Prerequisites
* Java 21 (I recommend [Amazon Corretto](https://aws.amazon.com/corretto))
* IDE (I recommend [IntelliJ IDEA](https://www.jetbrains.com/idea/))
#### Testing the localhost
#### Steps
Open the "localhost.exe" client.
If by any means the program did not open, and checking the server log your ping has been listened by the server
and you are using Windows 8, 10 or 11, it is probably some compatibility issue.
1. Clone Cosmic into a new project. In IntelliJ, you would create a new project from version control.
2. Open _config.yaml_. Find "DB_PASS" and set it to your database root user password.
3. Start the server. The main method is located in `net.server.Server`.
4. If you see "Cosmic is now online" in the console, it means the server is online and ready to serve traffic. Yay!
In some cases it helps to spam click the exe a few times (2-3 times usually works for me on W10).
Below, I list other ways of running the server which are completely optional.
In that case, extract "lolwut.exe" from "lolwut-v0.01.rar" and place it on the MapleStory client folder ("C:\Nexon\MapleStory").
Your "localhost.exe" property settings must follow these:
#### Docker
Support for Docker is also provided out of the box, as an alternative to running straight in the IDE. If you have [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed it's as easy as running `docker compose up`.
Note: "lolwut.exe" is currently not available in the Google Drive.
Making changes becomes a bit more tedious though as you have to rebuild the server image via `docker compose up --build`.
* Run in compatibility mode: Windows 7;
* Unchecked reduced color mode;
* 640 x 480 resolution;
* Unchecked disable display on high DPI settings;
* Run as an administrator;
* Opening "lolwut.exe", use Fraysa's method.
#### Jar
Another option is to start the server from a terminal by running a jar file. You first need to build the jar file from source which requires [Maven](https://maven.apache.org/). Fortunately, [Maven Wrapper](https://maven.apache.org/wrapper/) is provided so you don't have to install Maven separately.
Important: should the client be refused a connection to the game server, it may be because of firewall issues. Head to the end of this file to proceed in allowing this connection through the computer's firewall. Alternatively, one can deactivate the firewall and try opening the client again.
You can also search the server logs (/logs/cosmic-log.log) if any connection attempts have been made to ease debugging.
Building the jar file is as easy as running ``./mvnw.cmd clean package``. The project is configured to produce a "fat" jar which contains all dependencies (by utilizing the _maven-assembly-plugin_). Note that the WZ XML files are __not__ included in the jar.
---
### Installing the server
1. Configure the project
2. Set up the database
3. Launch the server
To run the jar, a ``launch.bat`` file is provided for convenience. Simply double-click it and the server will start in a new terminal window.
If you are using Docker (quick start):
1. Configure the project
2. Launch the server
Alternatively, run the jar file from the terminal. Just remember to provide the `wz-path` system property pointing to your wz directory.
#### Configuring the project
### 3 - Client
The client files are located in a separate repository: https://github.com/P0nk/Cosmic-client
The easiest way to set up your project is to clone the repository directly into a new IntelliJ project.
Follow the installation guide in the README.
1. Install IntelliJ
2. Create a new "Project from Version Control..."
3. Enter the URL to this GitHub repository: "https://github.com/P0nk/Cosmic.git"
4. Click on "Clone". A new project will now be created with all the files from the repository.
### 4 - Getting into the game
You have successfully started the client, and you're looking at the login screen.
#### Setting up the database
1. Install MySQL Server 8 and MySQL Workbench 8.
2. Using Workbench, create a new user with username "cosmic_server" and password "snailshell".
This the default configuration in Cosmic.
* (Optional) Restrict the Schema Privileges for this new user for improved security.
Add a new entry with "Schemas matching pattern: cosmic" and only select "SELECT", "INSERT", "UPDATE", "DELETE" under "Object Rights"
3. Run the sql scripts in the "database/sql" directory of the project in the order indicated by their names.
* Make sure you are connected to the database with the "root" user to be able to run the scripts.
* Run scripts one by one through the menu: "File" -> "Run SQL Script" -> select the script file to run -> "Run"
* The 3rd script "3-db_shopupdate" is optional. It adds custom shop items for certain NPCs.
* The 4th script "4-db_admin" is also optional, but recommended if you are new. It adds an admin account to simplify the setup.
Use this info when you connect to MySQL Server for the first time:
* Server Host: localhost
* Port: 3306
* Username: root
* Password: <whatever password you set during MySQL Server installation>
At the end of the execution of these sql scripts, you should have installed a database schema named "cosmic".
REGISTER YOUR FIRST ACCOUNT to be used in-game by **manually creating** an entry in the table "accounts" in the database with a username and password.
### Running the server
Configure the IP you want to use for your MapleStory server in "config.yaml" file, or set it as "localhost" if you want to run it only on your machine.
Alternatively, you can use the IP given by Hamachi to use on a Hamachi network, or you can use a non-Hamachi method of port-forwarding. Neither will be approached here.
To launch the server, you may either:
* Launch inside IntelliJ
* Launch a built jar file
* Launch with Docker
#### Run inside IntelliJ
1. Open the file src/main/java/net/server/Server.java.
2. Click the green arrow to the left of the class definition "public class Server", and then "Run Cosmic".
* Alternatively (recommended), create a new Configuration that points to "net.server.Server".
3. The server launches in a terminal window inside IntelliJ.
#### Run from a jar file
1. Create the jar file
* The jar file is created by the Maven assembly plugin in the package lifecycle.
* If you already have Maven installed, simply run the command "mvn clean install" to create the jar file.
* IntelliJ also comes with built-in Maven support. Open a new terminal window inside IntelliJ, type "mvn clean install" (your command should now be marked green), then Ctrl+Enter to build the jar file.
2. Launch the jar file
* Double click on "launch.bat" (need to have Java 17 installed)
#### Run as containers with Docker
1. Start Docker
2. Run the command "docker compose up" at the root of the project.
* If you make any changes to the code, make sure you append the "--build" option at the end of the command to force rebuild the server image.
---
### Getting into the game
If you ran the admin sql script, there already exists an account in the database with an admin character on it (GM level 6).
Log in using these credentials:
#### Logging in
At this point, you can log in to the admin account using the following credentials:
* Username: "admin"
* Password: "admin"
* Pin: "0000"
* Pic: "000000"
Admin characters have "hide" mode enabled by default. This means your character will be translucent on your screen, and completely invisible to others.
It will also prevent you from controlling mobs (making them stand still). To toggle this mode on and off, type "@hide" in the in-game chat.
You can also create a new regular account by typing in your desired username & password and attempting to log in. This "automatic registration" feature lets you create new accounts to play around with. It is enabled by default (see _config.yaml_).
By default, the server source is set to allow AUTO-REGISTERING. This means that, by simply typing in a "Login ID" and a "Password", you're able to create a new account.
#### Entering the game
Create a new character as you normally would, and then select it to enter the game. Hooray, finally we're in!
After creating a character, experiment typing in all-chat "@commands".
This will display all available commands for the current GM level your character has.
If you log in to the "Admin" character, you'll notice that the character looks almost invisible. This is hide mode, which is enabled by default when you log in to a GM character. You won't be visible to normal players and no mobs will move if you're alone on the map. Toggle hide mode on or off by typing "@hide" in the in-game chat.
To change a character's GM level, make sure that character is not logged in, then:
Hide is one of many commands available to players, type "@commands" to see the full list. Higher ranked GMs have access to more powerful commands.
1. Open MySQL Workbench;
2. Expand "cosmic" schema;
3. Expand "Tables";
4. Right-click "characters" and click "Select Rows"
5. Find your character in Result Grid. Scroll to the right and find the "gm" column.
6. Edit your character's gm value and click "Apply", and then "Apply" again in the window that appeared, then "Finish".
* 0 is what ordinary players start with, and 6 is the highest gm value. Higher level gms have access to more commands in game.
That's it, have fun playing around in game!
---
### Some notes about WZ/WZ.XML EDITING
Brief introduction to WZ files: they are the asset/data files required by the client and server. The client can read the .wz files directly, but the server requires them in XML format.
The server also does not make use of any of the sprites, which is where different kinds of exporting comes into the picture. HaRepacker allows you to export to Private server XML, which is the .img files packaged in the .wz stripped of sprites and converted to XML.
## Advanced concepts
Some slightly more advanced concepts that might be useful once you're up and running.
Link to HaRepacker-resurrected, the standard tool for handling WZ files: https://github.com/lastbattle/Harepacker-resurrected
### Host on remote server
You don't have to host the server on your local machine to play. It's possible to host on a remote server such as a VPS or a dedicated server.
NOTE: Be extremely wary when using server-side's XMLs data being reimported into the client's WZ, as some means of synchronization between the server and client modules, this action COULD generate some kind of bugs afterwards. Client-to-server data reimporting seems to be fine, though.
I leave it to you to figure out the server hosting part, but once you have that running you'll need to edit the client ip to point to your remote server ip.
#### Editing the v83 WZ's:
### WZ files
WZ files are the asset/data files required by the client and server. Typically, the [HaRepacker-resurrected](https://github.com/lastbattle/Harepacker-resurrected) tool is used to manage (view, edit, export) the .wz files.
The client can read the .wz files directly, but the server requires them to be in XML format. The server does not make use of the sprites, which is the motivation for different kinds of exporting.
HaRepacker allows you to export to "Private server", which is the .img files packaged in the .wz stripped of sprites and converted to XML. This takes much less disk space.
* Use the HaRepacker-resurrected 4.2.4 editor, encryption "GMS (old)".
* Open the desired WZ for editing and use the node hierarchy to make the desired changes (copy/pasting nodes may be unreliable in rare scenarios).
* Save the changed WZ, **overwriting the original content** at the client folder.
* Finally, **RE-EXPORT (using the "Private Server..." exporting option) the changed XMLs into the server's WZ.XML files**, overwriting the old contents.
This server requires custom .wz files (unfortunately), as you may have noted during installation of the client. The intention is for these to be removed eventually and to solely run on vanilla .wz files.
**These steps are IMPORTANT, to maintain synchronization** between the server and client modules.
#### WZ editing
* Use the HaRepacker-resurrected editor, encryption "GMS (old)".
* Open the desired .wz for editing and use the node hierarchy to make the desired changes (copy/pasting nodes may be unreliable in rare scenarios).
* Save the changed .wz, overwriting the original content at the client folder.
* Finally, re-export (using the "Private Server" exporting option) the changed XMLs into the server's .wz XML files (found in the "wz" directory), overwriting the old contents.
---
### Portforwarding the SERVER
Make sure to always export from the client .wz files to the server XML, and not the other way around.
To use portforward, you will need to have permission to change things on the LAN router. Access your router using the Internet browser. URLs vary accordingly with the manufacturer. To discover it, open the command prompt and type "ipconfig" and search for the "default gateway" field. The IP shown there is the URL needed to access the router. Also, look for the IP given to your machine (aka "IPv4 address" field), which will be the server one.
The default login/password also varies, so use the link http://www.routerpasswords.com/ as reference. Usually, login as "admin" and password as "password" completes the task well.
Now you have logged in the router system, search for anything related to portforwarding. Should the system prompt you between portforwarding and portriggering, pick the first, it is what we will be using.
Now, it is needed to enable the right ports for the Internet. For Cosmic, it is basically needed to open ports 7575 to 7575 + (number of channels) and port 8484. Create a new custom service which enables that range of ports for the server's channel and opt to use TCP/UDP protocols. Finally, create a custom service now for using port 8484.
Optionally, if you want to host a webpage, portforward the port 80 (the HTTP port) as well.
It is not done yet, sometimes the firewalls will block connections between the LAN and the Internet. To overcome this, it is needed to create some rules for the firewall to permit these connections. Search for the advanced options with firewalls on your computer and, with it open, create two rules (one outbound and one inbound).
These rules must target "one application", "enable connections" and must target your MapleStory client (aka localhost).
After all these steps, the portforwarding process should now be complete.
---
### Client changelog
The following list, in bottom-up chronological order,
holds information regarding all changes that were applied from the starting localhost used in this development.
Some lines have a link attached, that will lead you to a snapshot of the localhost at that version of the artifact.
Naturally, later versions holds all previous changes along with the proposed changes.
**Change log:**
* Fixed Monster Magnet crashing the caster when trying to pull fixed mobs, credits to Shavit. https://gofile.io/?c=BW7dVM (dead link)
* Cleared need for administrator privileges (OS) to play the game, credits to Ubaware.
* Set a higher cap for AP assigning with AP Reset, credits to Ubaware.
* Fixed Monster Magnet crashing the caster when trying to pull bosses. Drawback: Dojo HPBar becomes unavailable. https://hostr.co/SvnSKrGzXhG0
* Fixed some 'rn' problems with quest icons & removed "tab" from party leader changed message. https://hostr.co/tsYsQzzV6xT0
* Removed block on applying attack-based strengthening gems on non-weapon equipments. https://hostr.co/m2bVtnizCtmD
* Set a higher cap for SPEED.
* Removed the AP assigning block for beginners below level 10. https://hostr.co/AHAHzneCti9B
* Removed block on party for beginners level 10 or below. https://hostr.co/JZq53mMtToCz
* Removed block on MTS entering in some maps, rendering the buyback option available.
* Removed "AP excess" popup and limited actions on Admin/MWLB, credits to kevintjuh93.
* Removed "You've gained a level!" popup, credits to PrinceReborn.
* Removed caps for WATK, WDEF, MDEF, ACC, AVOID.
* 'n' problem fixed.
* Fraysa's https://hostr.co/gJbLZITRVHmv
* Eric's MapleSilver starting on window-mode.
Editing the client .wz without exporting to the server may lead to strange behavior.

View File

@@ -161,10 +161,18 @@ server:
#Database Configuration
DB_URL_FORMAT: "jdbc:mysql://%s:3306/cosmic" # If the docker ENV for DB_HOST is anything but "db", this string format should be changed from 3306 to 3307 (or whichever port it was changed to in docker)
DB_HOST: "localhost"
DB_USER: "cosmic_server"
DB_PASS: "snailshell"
DB_USER: "root"
DB_PASS: ""
INIT_CONNECTION_POOL_TIMEOUT: 90 # Seconds
PG_DB_URL: "jdbc:postgresql://localhost:5432/cosmic"
PG_DB_SCHEMA: "cosmic"
PG_DB_ADMIN_USERNAME: "cosmic_admin"
PG_DB_ADMIN_PASSWORD: "redsnailshell"
PG_DB_USERNAME: "cosmic_server"
PG_DB_PASSWORD: "bluesnailshell"
PG_DB_CLEAN: false # !!! WARNING !!! Deletes the entire database - starts from scratch.
#Login Configuration
WORLDS: 1 #Initial number of worlds on the server.
WLDLIST_SIZE: 21 #Max possible worlds on the server.
@@ -185,7 +193,6 @@ server:
BYPASS_PIN_EXPIRATION: 15 #Enables PIN bypass, which will remain active for that account by that client machine for N minutes. Set 0 to disable.
AUTOMATIC_REGISTER: true #Automatically register players when they login with a nonexistent username.
BCRYPT_MIGRATION: true #Performs a migration from old SHA-1 and SHA-512 password to bcrypt.
COLLECTIVE_CHARSLOT: false #Available character slots are contabilized globally rather than per world server.
DETERRED_MULTICLIENT: false #Enables detection of multi-client and suspicious remote IP on the login system.
#Besides blocking logging in with several client sessions on the same machine, this also blocks suspicious login attempts for players that tries to login on an account using several diferent remote addresses.
@@ -220,7 +227,6 @@ server:
USE_MTS: false
USE_CPQ: true #Renders the CPQ available or not.
USE_AUTOHIDE_GM: true #When enabled, GMs are automatically hidden when joining. Thanks to Steven Deblois (steven1152).
USE_BUYBACK_SYSTEM: false #Enables the HeavenMS-builtin buyback system, to be used by dead players when clicking the MTS button.
USE_FIXED_RATIO_HPMP_UPDATE: false #Enables the HeavenMS-builtin HPMP update based on the current pool to max pool ratio.
USE_FAMILY_SYSTEM: true
USE_DUEY: true
@@ -234,7 +240,7 @@ server:
USE_STARTING_AP_4: false #Use early-GMS 4/4/4/4 starting stats. To overcome AP shortage, this gives 4AP/5AP at 1st/2nd job advancements.
USE_AUTOBAN: false #Commands the server to detect infractors automatically.
USE_AUTOBAN_LOG: true #Log autoban related messages. Still logs even with USE_AUTOBAN disabled.
USE_AUTOSAVE: true #Enables server autosaving feature (saves characters to DB each 1 hour).
USE_EXP_GAIN_LOG: false #Logs characters exp gains; logs world rate & coupon exp, total gained exp, and current exp, level can be calculated from "ExpTable".
USE_SERVER_AUTOASSIGNER: false #HeavenMS-builtin autoassigner, uses algorithm based on distributing AP accordingly with required secondary stat on equipments.
USE_REFRESH_RANK_MOVE: true
USE_ENFORCE_ADMIN_ACCOUNT: false #Forces accounts having GM characters to be treated as a "GM account" by the client (localhost). Some of the GM account perks is the ability to FLY, but unable to TRADE.
@@ -246,10 +252,8 @@ server:
USE_ENFORCE_ITEM_SUGGESTION: false #Forces the Owl of Minerva and the Cash Shop to always display the defined item array instead of those featured by the players.
USE_ENFORCE_UNMERCHABLE_CASH: true #Forces players to not sell CASH items via merchants, drops of it disappears.
USE_ENFORCE_UNMERCHABLE_PET: true #Forces players to not sell pets via merchants, drops of it disappears. (since non-named pets gets dirty name and other possible DB-related issues)
USE_ENFORCE_MERCHANT_SAVE: true #Forces automatic DB save on merchant owners, at every item movement on shop.
USE_ENFORCE_MDOOR_POSITION: false #Forces mystic door to be spawned near spawnpoints.
USE_SPAWN_CLEAN_MDOOR: false #Makes mystic doors to be spawned without deploy animation. This clears disconnecting issues that may happen when trying to cancel doors a couple seconds after deployment.
USE_SPAWN_LOOT_ON_ANIMATION: false #Makes loot appear some time after the mob has been killed (following the mob death animation, instead of instantly).
USE_SPAWN_RELEVANT_LOOT: true #Forces to only spawn loots that are collectable by the player or any of their party members.
USE_ERASE_PERMIT_ON_OPENSHOP: true #Forces "shop permit" item to be consumed when player deploy his/her player shop.
USE_ERASE_UNTRADEABLE_DROP: true #Forces flagged untradeable items to disappear when dropped.
@@ -257,10 +261,8 @@ server:
USE_BUFF_MOST_SIGNIFICANT: true #When applying buffs, the player will stick with the highest stat boost among the listed, rather than overwriting stats.
USE_BUFF_EVERLASTING: false #Every applied buff on players holds expiration time so high it'd be considered permanent. Suggestion thanks to Vcoc.
USE_MULTIPLE_SAME_EQUIP_DROP: true #Enables multiple drops by mobs of the same equipment, number of possible drops based on the quantities provided at the drop data.
USE_BANISHABLE_TOWN_SCROLL: false #Enables town scrolls to act as if it's a "player banish", rendering the antibanish scroll effect available.
USE_ENABLE_FULL_RESPAWN: false #At respawn task, always respawn missing mobs when they're available. Spawn count doesn't depend on how many players are currently there.
USE_ENABLE_CHAT_LOG: false #Write in-game chat to log
USE_REBIRTH_SYSTEM: false #Flag to enable/disable rebirth system
USE_MAP_OWNERSHIP_SYSTEM: false #Flag to enable/disable map ownership system
USE_FISHING_SYSTEM: false #Flag to enable/disable custom fishing system
USE_NPCS_SCRIPTABLE: true #Flag to enable/disable serverside predefined script NPCs.
@@ -286,10 +288,6 @@ server:
USE_MAKER_PERMISSIVE_ATKUP: false #Allows players to use attack-based strengthening gems on non-weapon items.
USE_MAKER_FEE_HEURISTICS: true #Apply compiled values for stimulants and reagents into the Maker fee calculations (max error revolves around 50k mesos). Set false to use basic constant values instead (results are never higher than requested by the client-side).
#Custom Configuration
USE_ENABLE_CUSTOM_NPC_SCRIPT: false #Enables usage of custom HeavenMS NPC scripts (Agent E, Coco, etc). Will not disable Abdula (it's actually useful for the gameplay) or quests.
USE_STARTER_MERGE: false #Allows any players to use the Equipment Merge custom mechanic (as opposed to the high-level, Maker lv3 requisites).
#Commands Configuration
BLOCK_GENERATE_CASH_ITEM: false #Prevents creation of cash items with the item/drop command.
USE_WHOLE_SERVER_RANKING: false #Enables a ranking pool made from every character registered on the server for the "ranks" command, instead of separated by worlds.
@@ -318,7 +316,6 @@ server:
NAME_CHANGE_COOLDOWN: 2592000000 # (30*24*60*60*1000) Cooldown for name changes, default (GMS) is 30 days.
WORLD_TRANSFER_COOLDOWN: 2592000000 # (30*24*60*60*1000) Cooldown for world tranfers, default is same as name change (30 days).
INSTANT_NAME_CHANGE: false #Whether or not to wait for server restart to apply name changes. Does on reconnect otherwise (requires queries on every login).
REBIRTH_NPC_ID: 9010021 #ID of the NPC that should be replaced with the rebirth mechanic, if enabled.
#Dangling Items/Locks Configuration
ITEM_EXPIRE_TIME: 180000 # (3 * 60 * 1000) Time before items start disappearing. Recommended to be set up to 3 minutes.
@@ -346,10 +343,9 @@ server:
USE_PERFECT_SCROLLING: false #Scrolls doesn't use slots upon failure.
USE_ENHANCED_CHSCROLL: false #Equips even more powerful with chaos upgrade.
USE_ENHANCED_CRAFTING: false #Apply chaos scroll on every equip crafted.
USE_ENHANCED_CLNSLATE: false #Clean slates can be applied to recover successfully used slots as well.
SCROLL_CHANCE_ROLLS: 1 #Number of rolls for success on a scroll, set 1 for default.
CHSCROLL_STAT_RATE: 1 #Number of rolls of stat upgrade on a successfully applied chaos scroll, set 1 for default.
CHSCROLL_STAT_RANGE: 6 #Stat upgrade range (-N, N) on chaos scrolls.
CHSCROLL_STAT_RANGE: 5 #Stat upgrade range (-N, N) on chaos scrolls.
#Beginner Skills Configuration
USE_ULTRA_NIMBLE_FEET: false #Massive speed & jump upgrade.
@@ -364,7 +360,6 @@ server:
USE_FULL_HOLY_SYMBOL: false #Holy symbol doesn't require EXP sharers to work in full.
#Character Configuration
USE_ADD_SLOTS_BY_LEVEL: false #Slots are added each 20 levels.
USE_ADD_RATES_BY_LEVEL: false #Rates are added each 20 levels.
USE_STACK_COUPON_RATES: false #Multiple coupons effects builds up together.
USE_PERFECT_PITCH: false #For lvl 30 or above, each lvlup grants player 1 perfect pitch.
@@ -372,12 +367,6 @@ server:
#Quest Configuration
USE_QUEST_RATE: false #Exp/Meso gained by quests uses fixed server exp/meso rate times quest rate as multiplier, instead of player rates.
#Quest Points Configuration
QUEST_POINT_REPEATABLE_INTERVAL: 25 #Minimum interval between repeatable quest completions for quest points to be awarded.
QUEST_POINT_REQUIREMENT: 0 #Exchange factor between N quest points to +1 fame, set 0 to disable the entire quest point mechanism.
QUEST_POINT_PER_QUEST_COMPLETE: 0 #Each completed quest awards N quest points, set 0 to disable.
QUEST_POINT_PER_EVENT_CLEAR: 0 #Each completed event instance awards N quest points, set 0 to disable.
#Guild Configuration
CREATE_GUILD_MIN_PARTNERS: 6 #Minimum number of members on Guild Headquarters to establish a new guild.
CREATE_GUILD_COST: 1500000
@@ -396,7 +385,6 @@ server:
USE_EQUIPMNT_LVLUP_SLOTS: false #Equips can upgrade slots at level up.
USE_EQUIPMNT_LVLUP_POWER: false #Enable more powerful stat upgrades at equip level up.
USE_EQUIPMNT_LVLUP_CASH: false #Enable equip leveling up on cash equipments as well.
USE_SPIKES_AVOID_BANISH: false #Shoes equipped with spikes prevents mobs from banishing wearer.
MAX_EQUIPMNT_LVLUP_STAT_UP: 10000 #Max stat upgrade an equipment can have on a levelup.
MAX_EQUIPMNT_STAT: 32767 #Max stat on an equipment by leveling up.
USE_EQUIPMNT_LVLUP: 1 #All equips lvlup at max level of N, set 1 to disable.
@@ -447,14 +435,6 @@ server:
WEDDING_GIFT_LIMIT: 1 #Max number of gifts per person to same wishlist on marriage instances.
WEDDING_BLESSER_SHOWFX: true #Pops bubble sprite effect on players blessing the couple. Setting this false shows the blessing effect on the couple instead.
#Buyback Configuration
USE_BUYBACK_WITH_MESOS: true #Enables usage of either mesos or NX for the buyback fee.
BUYBACK_FEE: 77.70 #Sets the base amount needed to buyback (level 30 or under will use the base value).
BUYBACK_LEVEL_STACK_FEE: 85.47 #Sets the level-stacking portion of the amount needed to buyback (fee will sum up linearly until level 120, when it reaches the peak).
BUYBACK_MESO_MULTIPLIER: 1000 #Sets a multiplier for the fee when using meso as the charge unit.
BUYBACK_RETURN_MINUTES: 1 #Sets the maximum amount of time the player can wait before decide to buyback.
BUYBACK_COOLDOWN_MINUTES: 7 #Sets the time the player must wait before using buyback again.
# Login timeout by shavit
TIMEOUT_DURATION: 3600000 # Kicks clients who don't send any packet to the game server in due time (in millisseconds).

2
database/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
docker-db-data/*
docker-pg-db-data/*

View File

@@ -1,3 +0,0 @@
*
*/
!.gitignore

View File

@@ -0,0 +1,3 @@
CREATE DATABASE cosmic;
CREATE USER cosmic_admin WITH CREATEROLE ENCRYPTED PASSWORD 'redsnailshell';
GRANT ALL PRIVILEGES ON DATABASE cosmic TO cosmic_admin;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,351 +0,0 @@
#THIS SQL IS OPTIONAL, TO BE USED AFTER 'db_drops.sql'
#THIS REQUIRES PROVIDED WZ FILES
USE `cosmic`;
# Scroll shop at Asia, chair shop at Kino Konoko, potion shop at T-1337
INSERT INTO `shops` (`shopid`,`npcid`) VALUES
(2082014,2082014),
(9110002,9110002),
(9201101,9201101);
INSERT IGNORE INTO `shopitems` (`shopid`, `itemid`, `price`, `pitch`, `position`) VALUES
(2082014, 2040004, 480000, 0, 1),
(2082014, 2040025, 500000, 0, 2),
(2082014, 2040029, 500000, 0, 3),
(2082014, 2040017, 500000, 0, 4),
(2082014, 2040301, 400000, 0, 5),
(2082014, 2040317, 400000, 0, 6),
(2082014, 2040321, 400000, 0, 7),
(2082014, 2040413, 400000, 0, 8),
(2082014, 2040418, 400000, 0, 9),
(2082014, 2040501, 250000, 0, 10),
(2082014, 2040513, 250000, 0, 11),
(2082014, 2040516, 250000, 0, 12),
(2082014, 2040532, 250000, 0, 13),
(2082014, 2040613, 400000, 0, 14),
(2082014, 2040701, 450000, 0, 15),
(2082014, 2040704, 450000, 0, 16),
(2082014, 2040707, 450000, 0, 17),
(2082014, 2040804, 550000, 0, 18),
(2082014, 2040817, 550000, 0, 19),
(2082014, 2040914, 480000, 0, 20),
(2082014, 2040919, 480000, 0, 21),
(2082014, 2041007, 470000, 0, 22),
(2082014, 2041010, 470000, 0, 23),
(2082014, 2041013, 500000, 0, 24),
(2082014, 2041016, 500000, 0, 25),
(2082014, 2041019, 500000, 0, 26),
(2082014, 2041022, 500000, 0, 27),
(2082014, 2044901, 520000, 0, 28),
(2082014, 2044701, 520000, 0, 29),
(2082014, 2043001, 520000, 0, 30),
(2082014, 2043801, 520000, 0, 31),
(2082014, 2044601, 520000, 0, 32),
(2082014, 2040727, 50000, 0, 33),
(2082014, 2041058, 50000, 0, 34),
(2082014, 2040807, 1000000, 0, 35),
(2082014, 2040005, 22000, 0, 36),
(2082014, 2040026, 23000, 0, 37),
(2082014, 2040031, 23000, 0, 38),
(2082014, 2040016, 23000, 0, 39),
(2082014, 2040302, 25000, 0, 40),
(2082014, 2040318, 25000, 0, 41),
(2082014, 2040323, 25000, 0, 42),
(2082014, 2040412, 20000, 0, 43),
(2082014, 2040419, 20000, 0, 44),
(2082014, 2040502, 25000, 0, 45),
(2082014, 2040514, 25000, 0, 46),
(2082014, 2040517, 25000, 0, 47),
(2082014, 2040534, 25000, 0, 48),
(2082014, 2040612, 20000, 0, 49),
(2082014, 2040702, 20000, 0, 50),
(2082014, 2040705, 25000, 0, 51),
(2082014, 2040708, 20000, 0, 52),
(2082014, 2040805, 100000, 0, 53),
(2082014, 2040816, 100000, 0, 54),
(2082014, 2040915, 55000, 0, 55),
(2082014, 2040920, 55000, 0, 56),
(2082014, 2041008, 38000, 0, 57),
(2082014, 2041011, 38000, 0, 58),
(2082014, 2041014, 40000, 0, 59),
(2082014, 2041017, 40000, 0, 60),
(2082014, 2041020, 40000, 0, 61),
(2082014, 2041023, 40000, 0, 62),
(2082014, 2044902, 50000, 0, 63),
(2082014, 2044702, 50000, 0, 64),
(2082014, 2043002, 50000, 0, 65),
(2082014, 2043802, 50000, 0, 66),
(2082014, 2044602, 50000, 0, 67),
(2082014, 2049200, 170000, 0, 68),
(2082014, 2049201, 220000, 0, 69),
(2082014, 2049202, 170000, 0, 70),
(2082014, 2049203, 220000, 0, 71),
(2082014, 2049204, 170000, 0, 72),
(2082014, 2049205, 220000, 0, 73),
(2082014, 2049206, 170000, 0, 74),
(2082014, 2049207, 220000, 0, 75),
(2082014, 2049208, 140000, 0, 76),
(2082014, 2049209, 170000, 0, 77),
(2082014, 2049210, 140000, 0, 78),
(2082014, 2049211, 170000, 0, 79),
(2082014, 2040101, 540000, 0, 80),
(2082014, 2040100, 700000, 0, 81),
(2082014, 2040106, 540000, 0, 82),
(2082014, 2040105, 700000, 0, 83),
(2082014, 2040201, 540000, 0, 84),
(2082014, 2040200, 700000, 0, 85),
(2082014, 2040206, 540000, 0, 86),
(2082014, 2040205, 700000, 0, 87),
(2082014, 2070016, 120000000, 0, 88),
(2082014, 2070018, 190000000, 0, 89),
(2082014, 2030007, 1800000, 0, 90),
(2082014, 4001017, 60000000, 0, 91);
UPDATE shopitems SET price = 11*price WHERE (`position` >= 33 and `position` <= 79 and `shopid` = 2082014);
INSERT IGNORE INTO `shopitems` (`shopid`, `itemid`, `price`, `pitch`, `position`) VALUES
(1031100, 3010015, 20000, 0, 100),
(9110002, 3010019, 7700000, 0, 92),
(9110002, 3010008, 10000000, 0, 96),
(9110002, 3010007, 10000000, 0, 100),
(9201020, 3010009, 4200000, 0, 96),
(9201020, 3010014, 7000000, 0, 100),
(1081000, 3010013, 4000000, 0, 100),
(9201101, 2022338, 2100000, 0, 100),
(9201101, 2022339, 2800000, 0, 104),
(9201101, 2022340, 4000000, 0, 108),
(9201101, 2022341, 2800000, 0, 112),
(9201101, 2022342, 2000000, 0, 116),
(9201101, 2022343, 4700000, 0, 120),
(9201101, 2022344, 5000000, 0, 124),
(9201101, 2022345, 4000000, 0, 128),
(9201101, 2002028, 5000000, 0, 132),
(9201101, 2022544, 100000, 0, 136),
(9201101, 2022545, 4400000, 0, 140),
(9201101, 2020027, 2100000, 0, 144),
(9201101, 2022113, 1800000, 0, 148),
(9201101, 2022121, 12000000, 0, 152),
(9201101, 2022123, 12000000, 0, 156),
(9201101, 2022277, 5300000, 0, 160),
(9201101, 2012008, 4200000, 0, 164),
(9201101, 2022251, 3800000, 0, 168);
# adding antibanish scrolls
INSERT IGNORE INTO `shopitems` (`shopid`, `itemid`, `price`, `pitch`, `position`) VALUES
(1001100, 2030100, 450, 0, 130),
(1011100, 2030100, 450, 0, 142),
(1021100, 2030100, 450, 0, 142),
(1031100, 2030100, 450, 0, 146),
(1051002, 2030100, 450, 0, 142),
(1052116, 2030100, 450, 0, 118),
(1061001, 2030100, 450, 0, 126),
(1061002, 2030100, 450, 0, 130),
(1091002, 2030100, 450, 0, 130),
(1100002, 2030100, 450, 0, 138),
(2012005, 2030100, 450, 0, 126),
(2022001, 2030100, 450, 0, 126),
(2030009, 2030100, 450, 0, 126),
(2040051, 2030100, 450, 0, 102),
(2041006, 2030100, 450, 0, 134),
(2051000, 2030100, 450, 0, 134),
(2060004, 2030100, 450, 0, 134),
(2070001, 2030100, 450, 0, 134),
(2080001, 2030100, 450, 0, 134),
(2090003, 2030100, 450, 0, 126),
(2093002, 2030100, 450, 0, 126),
(2100004, 2030100, 450, 0, 130),
(2110001, 2030100, 450, 0, 130),
(2130000, 2030100, 450, 0, 126),
(9201060, 2030100, 450, 0, 114),
(9270021, 2030100, 450, 0, 118),
(9270022, 2030100, 450, 0, 114), -- Thanks Rednor for finding duplicate item on NPC
(1338, 2030100, 450, 0, 114),
(9270057, 2030100, 450, 0, 4),
(9270065, 2030100, 450, 0, 3);
-- Thanks to Vcoc
-- GMShop: Sacks, GmEquip, Cheese & Onyx, Utils,
-- Arrows, Bullets, Throwings and Capsules,
-- Others, Equips, Mounts, Scrolls.
DELETE FROM `shopitems` WHERE `shopid`=1337;
INSERT INTO `shopitems` ( `shopid`, `itemid`, `price`, `position`) VALUES
(1337, 2100036, 1, 1),
(1337, 2100035, 1, 2),
(1337, 2100034, 1, 3),
(1337, 2100033, 1, 4),
(1337, 2100007, 1, 5),
(1337, 2100006, 1, 6),
(1337, 2100005, 1, 7),
(1337, 2100004, 1, 8),
(1337, 2100003, 1, 9),
(1337, 2100002, 1, 10),
(1337, 2100001, 1, 11),
(1337, 1002959, 1, 12),
(1337, 1002140, 1, 13),
(1337, 1042003, 1, 14),
(1337, 1062007, 1, 15),
(1337, 1322013, 1, 16),
(1337, 1072010, 1, 17),
(1337, 2022179, 1, 18),
(1337, 2022273, 1, 19),
(1337, 2041200, 1, 20),
(1337, 4006001, 1, 21),
(1337, 4001017, 1, 22),
(1337, 4031179, 1, 23),
(1337, 2070018, 1, 24),
(1337, 2060004, 1, 25),
(1337, 2061004, 1, 26),
(1337, 2330005, 1, 27),
(1337, 2332000, 1, 28),
(1337, 2331000, 1, 29),
(1337, 5072000, 1, 30),
(1337, 5390000, 1, 31),
(1337, 5390001, 1, 32),
(1337, 5390002, 1, 33),
(1337, 5390005, 1, 34),
(1337, 5390006, 1, 35),
(1337, 1492013, 1, 36),
(1337, 1482013, 1, 37),
(1337, 1452044, 1, 38),
(1337, 1472052, 1, 39),
(1337, 1462039, 1, 40),
(1337, 1332050, 1, 41),
(1337, 1312031, 1, 42),
(1337, 1322052, 1, 43),
(1337, 1302059, 1, 44),
(1337, 1442045, 1, 45),
(1337, 1432038, 1, 46),
(1337, 1382036, 1, 47),
(1337, 1412026, 1, 48),
(1337, 1422028, 1, 49),
(1337, 1402036, 1, 50),
(1337, 1372032, 1, 51),
(1337, 1122000, 1, 52),
(1337, 1082149, 1, 53),
(1337, 1912000, 1, 54),
(1337, 1902000, 1, 55),
(1337, 1902001, 1, 56),
(1337, 1902002, 1, 57),
(1337, 1912005, 1, 58),
(1337, 1902005, 1, 59),
(1337, 1902006, 1, 60),
(1337, 1902007, 1, 61),
(1337, 1912011, 1, 62),
(1337, 1902015, 1, 63),
(1337, 1902016, 1, 64),
(1337, 1902017, 1, 65),
(1337, 1902018, 1, 66),
(1337, 2044908, 1, 67),
(1337, 2044815, 1, 68),
(1337, 2044512, 1, 69),
(1337, 2044712, 1, 70),
(1337, 2044612, 1, 71),
(1337, 2043312, 1, 72),
(1337, 2043117, 1, 73),
(1337, 2043217, 1, 74),
(1337, 2043023, 1, 75),
(1337, 2044417, 1, 76),
(1337, 2044317, 1, 77),
(1337, 2043812, 1, 78),
(1337, 2044117, 1, 79),
(1337, 2044217, 1, 80),
(1337, 2044025, 1, 81),
(1337, 2043712, 1, 82),
(1337, 2340000, 1, 83),
(1337, 2040807, 1, 84),
(1337, 2210032, 1, 85),
(1337, 2050004, 1, 86);
-- Thanks to DietStory v1.02 dev team
INSERT INTO `shopitems` ( `shopid`, `itemid`, `price`, `pitch`, `position`) VALUES
(1200001, 3010001, 1000, 0, 1),
(1200001, 1092003, 2000, 0, 2),
(1200001, 1072063, 10000, 0, 3),
(1200001, 1072062, 10000, 0, 4),
(1200001, 1072017, 10000, 0, 5),
(1200001, 1072049, 5000, 0, 6),
(1200001, 1072048, 5000, 0, 7),
(1200001, 1072008, 5000, 0, 8),
(1200001, 1072005, 50, 0, 9),
(1200001, 1072038, 50, 0, 10),
(1200001, 1072037, 50, 0, 11),
(1200001, 1072001, 50, 0, 12),
(1200001, 1062001, 4800, 0, 13),
(1200001, 1062000, 4800, 0, 14),
(1200001, 1060004, 2800, 0, 15),
(1200001, 1060007, 1000, 0, 16),
(1200001, 1041012, 3000, 0, 17),
(1200001, 1041004, 3000, 0, 18),
(1200001, 1040014, 3000, 0, 19),
(1200001, 1040013, 3000, 0, 20),
(1200001, 1002001, 3000, 0, 21),
(1200001, 1002019, 2000, 0, 22),
(1200001, 1002134, 800, 0, 23),
(1200001, 1002133, 800, 0, 24),
(1200001, 1002132, 800, 0, 25),
(1200001, 1002069, 450, 0, 26),
(1200001, 1002068, 450, 0, 27),
(1200001, 1002067, 450, 0, 28),
(1200001, 1002066, 450, 0, 29),
(1200001, 1002014, 1000, 0, 30),
(1200001, 1002008, 500, 0, 31),
(1200001, 1332007, 1000, 0, 32),
(1200001, 1312000, 3000, 0, 33),
(1200001, 1302007, 3000, 0, 34),
(1200001, 1322005, 50, 0, 35),
(1200001, 1312004, 50, 0, 36),
(1200001, 1302000, 50, 0, 37),
(1200002, 2330000, 600, 0, 104),
(1200002, 2070000, 500, 0, 108),
(1200002, 2061000, 1, 0, 120),
(1200002, 2060000, 1, 0, 124),
(1200002, 2030100, 400, 0, 128),
(1200002, 2030000, 400, 0, 132),
(1200002, 2020028, 3000, 0, 136),
(1200002, 2010004, 310, 0, 140),
(1200002, 2010003, 100, 0, 144),
(1200002, 2010001, 106, 0, 148),
(1200002, 2010002, 50, 0, 152),
(1200002, 2010000, 30, 0, 156),
(1200002, 2002005, 500, 0, 160),
(1200002, 2002004, 500, 0, 164),
(1200002, 2002002, 500, 0, 168),
(1200002, 2002001, 400, 0, 172),
(1200002, 2002000, 500, 0, 176),
(1200002, 2000006, 620, 0, 180),
(1200002, 2000003, 200, 0, 184),
(1200002, 2000002, 320, 0, 188),
(1200002, 2000001, 160, 0, 192),
(1200002, 2000000, 50, 0, 196),
(1301000, 2330000, 600, 0, 104),
(1301000, 2070000, 500, 0, 108),
(1301000, 2061000, 1, 0, 112),
(1301000, 2060000, 1, 0, 116),
(1301000, 2030100, 400, 0, 120),
(1301000, 2030000, 400, 0, 124),
(1301000, 2022000, 1650, 0, 128),
(1301000, 2022003, 1100, 0, 132),
(1301000, 2002005, 500, 0, 136),
(1301000, 2002004, 500, 0, 140),
(1301000, 2002002, 500, 0, 144),
(1301000, 2002001, 400, 0, 148),
(1301000, 2002000, 500, 0, 152),
(1301000, 2000006, 620, 0, 156),
(1301000, 2000003, 200, 0, 160),
(1301000, 2000002, 320, 0, 164),
(1301000, 2000001, 160, 0, 168),
(1301000, 2000000, 50, 0, 172);
# adding missing pirate items at Singapore npc's
INSERT INTO `shopitems` ( `shopid`, `itemid`, `price`, `pitch`, `position`) VALUES
(9270019, 1492006, 160000, 0, 80),
(9270019, 1492005, 100000, 0, 84),
(9270019, 1492004, 75000, 0, 88),
(9270019, 1482006, 150000, 0, 92),
(9270019, 1482005, 100000, 0, 96),
(9270019, 1482004, 75000, 0, 100),
(9270020, 1052113, 120000, 0, 92),
(9270020, 1052110, 100000, 0, 96),
(9270020, 1002625, 75000, 0, 100);

View File

@@ -1,87 +0,0 @@
-- MySQL dump 10.13 Distrib 8.0.22, for Win64 (x86_64)
--
-- Host: 127.0.0.1 Database: cosmic
-- ------------------------------------------------------
-- Server version 8.0.19
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
USE `cosmic`;
--
-- Dumping data for table `accounts`
--
LOCK TABLES `accounts` WRITE;
/*!40000 ALTER TABLE `accounts` DISABLE KEYS */;
INSERT INTO `accounts` VALUES (1,'admin','$2y$12$aFD9BDeUocDMY1X4tDYDyeJw/HhkQwCQWs3KAY7gCaRG0cpqJcaL.','0000','000000',0,'2021-05-24 00:00:01','2021-05-24 00:00:02','2005-05-11',0,NULL,NULL,1000000,1000000,1000000,3,0,'2005-05-11 03:00:00',0,1,NULL,0,NULL,0,NULL,NULL,0,0,'1234-5678',2);
/*!40000 ALTER TABLE `accounts` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Dumping data for table `characters`
--
LOCK TABLES `characters` WRITE;
/*!40000 ALTER TABLE `characters` DISABLE KEYS */;
INSERT INTO `characters` VALUES (1,1,0,'Admin',1,0,0,12,5,4,4,50,5,50,5,0,0,0,0,0,0,0,30030,20000,0,'0,0,0,0,0,0,0,0,0,0',10000,2,6,-1,25,'2021-05-24 00:00:03',1,0,1,0,0,5,0,4,1,0,0,0,0,0,0,0,0,0,0,24,24,24,24,-1,0,5,0,0,0,0,0,0,0,0,0,0,0,'','2021-05-24 00:00:04','2015-01-01 05:00:00',1,0);
/*!40000 ALTER TABLE `characters` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Dumping data for table `inventoryequipment`
--
LOCK TABLES `inventoryequipment` WRITE;
/*!40000 ALTER TABLE `inventoryequipment` DISABLE KEYS */;
INSERT INTO `inventoryequipment` VALUES (17,22,7,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,1,0,-1),(18,23,7,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,0,-1),(19,24,5,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,0,-1),(20,25,7,0,0,0,0,0,0,0,17,0,0,0,0,0,0,0,0,0,0,1,0,-1);
/*!40000 ALTER TABLE `inventoryequipment` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Dumping data for table `inventoryitems`
--
LOCK TABLES `inventoryitems` WRITE;
/*!40000 ALTER TABLE `inventoryitems` DISABLE KEYS */;
INSERT INTO `inventoryitems` VALUES (21,1,1,NULL,4161001,4,1,1,'',-1,0,-1,''),(22,1,1,NULL,1040002,-1,-5,1,'',-1,0,-1,''),(23,1,1,NULL,1060002,-1,-6,1,'',-1,0,-1,''),(24,1,1,NULL,1072001,-1,-7,1,'',-1,0,-1,''),(25,1,1,NULL,1302000,-1,-11,1,'',-1,0,-1,'');
/*!40000 ALTER TABLE `inventoryitems` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Dumping data for table `keymap`
--
LOCK TABLES `keymap` WRITE;
/*!40000 ALTER TABLE `keymap` DISABLE KEYS */;
INSERT INTO `keymap` VALUES (161,1,18,4,0),(162,1,65,6,106),(163,1,2,4,10),(164,1,23,4,1),(165,1,3,4,12),(166,1,4,4,13),(167,1,5,4,18),(168,1,6,4,24),(169,1,16,4,8),(170,1,17,4,5),(171,1,19,4,4),(172,1,25,4,19),(173,1,26,4,14),(174,1,27,4,15),(175,1,31,4,2),(176,1,34,4,17),(177,1,35,4,11),(178,1,37,4,3),(179,1,38,4,20),(180,1,40,4,16),(181,1,43,4,9),(182,1,44,5,50),(183,1,45,5,51),(184,1,46,4,6),(185,1,50,4,7),(186,1,56,5,53),(187,1,59,6,100),(188,1,60,6,101),(189,1,61,6,102),(190,1,62,6,103),(191,1,63,6,104),(192,1,64,6,105),(193,1,57,5,54),(194,1,48,4,22),(195,1,29,5,52),(196,1,7,4,21),(197,1,24,4,25),(198,1,33,4,26),(199,1,41,4,23),(200,1,39,4,27);
/*!40000 ALTER TABLE `keymap` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Dumping data for table `storages`
--
LOCK TABLES `storages` WRITE;
/*!40000 ALTER TABLE `storages` DISABLE KEYS */;
INSERT INTO `storages` VALUES (1,1,0,4,0);
/*!40000 ALTER TABLE `storages` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

View File

@@ -1,602 +0,0 @@
Oblivion Monk Trainee : Dark Katinas (Male)
Star Pixie : Star Pixie Starpiece
Star Pixie : Blue White-Line Boots
Spirit Viking : Scroll for Knuckler for Accuracy 10%
Spirit Viking : Spirit Viking Card
Spirit Viking : Dark Battle Lord
Spirit Viking : Heaven Justice
Iron Boar : Maroon Jangoon Boots
Iron Boar : Gold Tail
Iron Boar : Justice Katara
Voodoo : Steel Pow
Green Hobi : Pin Hov Charm
Green Hobi : Blue Stud
Zombie Lupin : Equip Enhancement Scroll
Zombie Lupin : Gold Mask
Reinforced Mithril Mutae : Reinforced Mithril Mutae Card
Yeti : Gold Tail
Blue Kentaurus : Kentaurus Marrow
Blue Kentaurus : Infinity Wrath
Iruvata : Iruvata Card
Iruvata : Heaven Justice
Snowman : Red-Bean Soup
White Fang : White Fang Tail
White Fang : Blue Jangoon Boots
Green Cornian : Cornian Marrow
Green Cornian : [Mastery Book] Venom
Green Cornian : Red Katte (Female)
Dodo : Whale Helmet
Dodo : Scroll for Overall Armor for STR 60%
Dodo : [Mastery Book] Slash Storm 20
Dodo : Dark Katte (Female)
Ultra Gray : Ultra Gray Card
Lorang : Lorang Claws
Lorang : Dark Piette Pants
Buffoon : Buffoon Grandpa Clock
Buffoon : Blue Battle Lord
Buffoon : Blue Stud
Buffoon : Infinity Wrath
Flaming Raccoon : Kinoko Ramen (Salt)
Red Kentaurus : Kentaurus Flame
Red Kentaurus : Scroll for Knuckler for Accuracy 60%
Red Kentaurus : Serpent Tongue
Red Kentaurus : Dragon Tail
Red Kentaurus : Heavenly Katara
Mighty Maple Eater : Fireman Axe
Tauromacis : Ascalon Katara
Tauromacis : Black Pirate Bandana
Krappy : Krappi Card
Roloduck : Blue Hunter Armor
Roloduck : Blue Hunter Pants
Crow : Fish Cake (Skewer)
Male Mannequin : Male Mannequin Fedora
Male Mannequin : Dark Scroll for Accessory for STR 70%
Male Mannequin : Male Mannequin Card
Male Mannequin : Majestic Katara
Leader B : Leader B Charm
Leader B : Kinoko Ramen (Roasted Pork)
Typhon : Dark Scroll for Cape for Weapon Def 70%
Leader A : Leader A Shades
Leader A : Dark Scroll for Cape for Magic Def 70%
Leader A : Serpent Tongue
Soul Teddy : Scroll for Pole Arm for Accuracy 10%
Soul Teddy : Steel Pow
Soul Teddy : Heavenly Katara
Beetle : Beetle Horn
Beetle : Equip Enhancement Scroll
Beetle : Ascalon Katara
Froscola : Blue Battle Lord
White Yeti and King Pepe : Scroll for Pole Arm for Accuracy 10%
White Yeti and King Pepe : King Pepe Scroll for One-handed BW Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Dagger Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Two-handed Sword Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Two-handed Axe Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Two-handed BW Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Spear Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Polearm Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Wand Magic Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Staff Magic Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Bow Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Crossbow Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Thief Attacks 60%
White Yeti and King Pepe : King Pepe Scroll for Knuckle Attacks 60%
Harp : Harp Tail Feather
Harp : Scroll for Knuckler for Accuracy 100%
Harp : Heavenly Katara
Harp : Infinity Wrath
Lucida : Green Pirate Bottom
Lucida : Brown Stud
Lucida : Brown Stud Pants
Jr. Grupin : Equip Enhancement Scroll
Iron Hog : Iron Hog Metal Hoof
Sophilia Doll : Fireman Axe
Sophilia Doll : Maroon Jangoon Boots
Tiru : Tiru Feather
Female Mannequin : Female Mannequin Wig
Female Mannequin : Dark Scroll for Accessory for STR 70%
Female Mannequin : Female Mannequin Card
Female Mannequin : Majestic Katara
Poison Golem Level 3 : Poison Golem Card
Malady : Malady Experimental Frog
Malady : Potential Scroll
Malady : Steel Pow
Vikerola : Dark Battle Lord
Wooden Target Dummy : Wooden Target Dummy Card
Wooden Target Dummy : Brown Stud
Wooden Target Dummy : Brown Stud Pants
Cynical Orange Mushroom : Basic Archer Glove
Extra A : Littleman A Badge
Extra A : Gold Snowboard
Extra B : Littleman B Name Plate
Extra B : Fairy Honey
Extra B : Brown Jangoon Boots
Hodori : Majestic Katara
Extra C : Littleman C Necklace
Extra D : Cat Eye
Extra D : Gold Snowboard
Extra D : Serpent Tongue
King Sage Cat : Scroll for Pole Arm for Accuracy 10%
King Sage Cat : Heaven Justice
King Sage Cat : Dragon Tail
King Sage Cat : Infinity Wrath
Homunscullo : Homunsculer Sand
Homunscullo : Homunsculer Blood
D. Roy : D. Roy Card
Nospeed : Arwen Glass Shoes
Ribbon Pig : Pig Ribbon
Ribbon Pig : Champion Katara
Grim Phantom Watch : [Storybook] Black Book
Grim Phantom Watch : Grim Phantom Watch Card
Qualm Guardian : Blue Katte (Female)
Qualm Guardian : Blue Katina Boots
Yeti Doll : Justice Katara
King Bloctopus : Green White-Line Boots
Bigfoot : Bigfoot Toe
Green King Goblin : Black Pirate Bandana
Dreamy Ghost : Kinoko Ramen (Roasted Pork)
MT-09 : MT-09 Fuel
Gryphon : Duck Tube [1]
Gryphon : Serpent Tongue
Risell Squid : Dark Pirate Bottom
Dark Axe Stump : Stump Teardrop
Blue Wyvern : Equip Enhancement Scroll
Nest Golem : Blue Katinas (Male)
Cheap Amplifier : Dark Scroll for Accessory for STR 70%
Cheap Amplifier : Cheap Amplifier Card
Manon : Manon Tail
Manon : Manon Cry
Saitie : Parwen Entry Pass
Saitie : Red Battle Lord
Drumming Bunny : Shoes Production Manual
Drumming Bunny : Scroll for Pole Arm for Accuracy 10%
Drumming Bunny : Gold Snowboard
Drumming Bunny : Lion Fang
Dyle : Scroll for Knuckler for Accuracy 60%
Crimson Tree : Crimson Wood
Crimson Tree : Crimson Wood
Blue Flower Serpent : Blue Flower Serpent Card
Blue Flower Serpent : Scroll for Pole Arm for Accuracy 100%
Blue Flower Serpent : Cat Eye
Spirit of Rock : Spirit of Rock Music Score
Spirit of Rock : Dark Scroll for Accessory for STR 70%
Spirit of Rock : Rock Spirit Card
Prototype Lord : Prototype Lord Card
Seruf : Lion Fang
Seruf : Serpent Tongue
Dark Wyvern : Blue Katina Boots
Mateon : Mateon Tentacle
Mateon : Equip Enhancement Scroll
Mateon : Dark Pennance
Mateon : Gold Pendant
Mr. Anchor : [Storybook] Black Book
Master Robo : Lion Fang
Master Robo : Brown Jangoon Boots
Master Robo : Gold Wings
Ginseng Jar : Ginseng Jar Card
Scuba Pepe : Equip Enhancement Scroll
Ghost Pirate : Blue Battle Lord
Ghost Pirate : Green Pirate Bottom
Ghost Pirate : Heavenly Katara
Leatty : Green White-Line Boots
Tiv : Tiv Feather
Gold Yeti and King Pepe : King Pepe Scroll for One-handed BW Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Dagger Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Two-handed Sword Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Two-handed Axe Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Two-handed BW Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Spear Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Polearm Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Wand Magic Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Staff Magic Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Bow Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Claw Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Crossbow Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Thief Attacks 60%
Gold Yeti and King Pepe : King Pepe Scroll for Knuckle Attacks 60%
Gold Yeti and King Pepe : Maroon Jangoon Boots
Slime : Champion Katara
Rodeo : Brown Stud
Rodeo : Brown Stud Pants
Octopus : Champion Katara
Luster Pixie : Luster Pixie Sunpiece
Luster Pixie : Luster Pixie Card
Nightshadow : Heaven Justice
Nightshadow : Infinity Wrath
Dual Beetle : Dual Beetle Horn
Skeleton Soldier : Skeleton Soldier Card
Master Death Teddy : Green Battle Lord
Master Death Teddy : Bloodsoaked Katara
Sakura Cellion : Maroon Jangoon Boots
Pig : Pig Head
Fire Boar : Fire Boar Tooth
Fire Boar : Arwen Glass Shoes
Fire Boar : Fire Boar Card
Panda Teddy : Maroon Jangoon Boots
Morphed Blin : Serpent Tongue
Morphed Blin : Steel Pow
Lunar Pixie : Lunar Pixie Moonpiece
Lunar Pixie : Gold Wings
Curse Eye : "Blackbull" deed to the land
Curse Eye : Gold Snowboard
Jr. Cellion : Cat Eye
Barnard Gray : Barnard Gray Card
Faust : Lupin Banana
Faust : Lion Fang
Faust : Serpent Tongue
Gatekeeper : Gate Keeper Card
Gatekeeper : Red Battle Lord
Griffey : [Mastery Book] Flame Wheel 20
Griffey : Meteor Katara
Shark : Heaven Justice
Deep Buffoon : Deep Buffoon Rock Piece
Deep Buffoon : Blue Stud
Deep Buffoon : Blazing Dragon Katara
Melon Bubble Tea : Melon Bubble Tea Card
Melon Bubble Tea : Maroon Jangoon Boots
Tino : Tino Feather
Bone Fish : Equip Enhancement Scroll
Bone Fish : Potential Scroll
I.AM.ROBOT : Lion Fang
I.AM.ROBOT : Serpent Tongue
Lilynouch : Knight Mask
Lilynouch : [Mastery Book] Dragon Breath
Dark Pepe : Serpent Coil
Dark Pepe : Serpent Coil
Toy Trojan : Toy Soldier Sword
Toy Trojan : Justice Katara
Sparker : Scroll for Knuckler for Accuracy 100%
Sparker : Justice Katara
Dark Drake : Dark Drake Horn
Electrophant : Serpent Tongue
Electrophant : Lion Fang
Tick-Tock : Tick-Tock Egg
Mecateon : Mecateon Laser Gun
Mecateon : Majestic Katara
Helmet Pepe : King Pepe Scroll for Polearm Attacks 60%
Helmet Pepe : King Pepe Scroll for Wand Magic Attacks 60%
Blue Mushroom : Thermal Fabric
Blue Mushroom : Purple Jewelry Shoes
Ratz : One-Handed Mace Forging Manual
Snow Witch : Ice Tears
Flyeye : Flyeye Wing
Flyeye : Flyeye Card
Headless Horseman : Devil Sunrise
Royal Guard : Royal Guard Card
Royal Guard : Scroll for Knuckler for Accuracy 10%
Royal Guard : Heaven Justice
Royal Guard : Dark Battle Lord
Triple Rumo : Blue Jangoon Boots
Triple Rumo : Brown Jangoon Boots
Pianus : Miniature Pianus
Pianus : Pianus Scream
Pianus : Scroll for Overall Armor for STR 60%
Pianus : [Mastery Book] Blaze 20
Pianus : Blue Katte (Female)
Pianus : Blue Katinas (Male)
Rombot : Rombot Memory Card
Rombot : Serpent Tongue
Roid : Potential Scroll
Roid : Equip Enhancement Scroll
Robby : Robby Electronic Induction Device
Robby : Robby Card
Robby : Blue Stud
Blue Snail : Grey/Brown Training Shirt
Blue Snail : Grey/Brown Training Pants
Afterlord : Afterlord Card
Afterlord : Scroll for Overall Armor for STR 60%
Afterlord : Heaven Gate
Afterlord : Bloodsoaked Katara
Block Golem : Block Golem Card
Lyka : Guardian Horn
Lyka : Dark Katinas (Male)
Jr. Wraith : Equip Enhancement Scroll
Jr. Wraith : Potential Scroll
Sentinel : Fireman Axe
Ice Drake : Blue Battle Lord
Ice Drake : Gold Mask
Overlord A : Overlord A Card
Overlord B : Overlord B Card
Latest Hits Compilation : Dark Scroll for Accessory for STR 70%
Latest Hits Compilation : Latest Hits Compilation Card
Water Goblin : Serpent Coil
Water Goblin : Serpent Coil
Retz : Retz Card
Retz : Cat Eye
Retz : Blue Jangoon Boots
Hoodoo : Steel Pow
Pink Bean : Pink Bean Card
Pink Bean : [Mastery Book] Maple Warrior 30
Paper Lantern Ghost : Fish Cake (Skewer)
Paper Lantern Ghost : Cat Eye
Cold Shark : Frozen Shark Fin
Cold Eye : Icicles
Croco : Potential Scroll
Croco : Gold Pendant
Croco : Majestic Katara
Bubble Fish : Bubble Fish Thoughts
Officer Skeleton : Serpent Coil
Officer Skeleton : Serpent Coil
Murumuru : Murumuru Furball
Zeno : Black Pirate Bandana
Panda : Ascalon Katara
Panda : Black Pirate Bandana
Iron Mutae : Iron Mutae Card
Snail : Red Mini Skirt
Hankie : Hankie Panfluit
Hankie : Heaven Justice
Silver Slime : Silver Slime Card
Silver Slime : Beige Elf Shoes
Zeta Gray : Zeta Gray Card
Zeta Gray : Dark Piette Pants
Zombie Mushroom : The Charm of the Undead
Zombie Mushroom : Hunter Bow
Zombie Mushroom : Guardian Katara
Pepe : Majestic Katara
Grupin : Icicles
Grupin : Equip Enhancement Scroll
Hobi : Hov Shorts
Yabber Doo : Beige Elf Shoes
Royal Cactus : Dark Hunter Armor
Royal Cactus : Dark Hunter Pants
Dual Ghost Pirate : Dual Pirate Propeller
Dual Ghost Pirate : Infinity Wrath
Memory Monk Trainee : [Mastery Book] Venom 30
Memory Monk Trainee : Devil Sunrise
Coolie Zombie : Zombie Lost Tooth
Coolie Zombie : [Storybook] Black Book
Coolie Zombie : Brown Stud
Coolie Zombie : Brown Stud Pants
Blood Harp : Blood Harp Crown
Blood Harp : Heavenly Katara
Black Kentaurus : Kentaurus Skull
Black Kentaurus : Gold Arund
Black Kentaurus : Blazing Dragon Katara
Rash : Rash Furball
Rash : Anasthetic Powder
Imperial Guard : Imperial Guard Card
The Book Ghost : The Book Ghost Sheet of Paper
The Book Ghost : Blue Jangoon Boots
Drake : Drake Meat
Drake : Drake Blood
Oblivion Guardian : [Mastery Book] Blessing of the Onyx 30
Zakum3 : [Skill Book] Advanced Combo
Zakum3 : [Mastery Book] Smoke Bomb
Zakum3 : [Mastery Book] Sanctuary
Zakum3 : [Mastery Book] Venom
Zakum3 : [Mastery Book] Crossbow Expert
Zakum3 : Zakum Helmet (1)
Zakum3 : Devil Sunrise
Zakum3 : Zakum Tree Branch
Jr. Newtie : Equip Enhancement Scroll
Jr. Newtie : Devil Sunrise
Jr. Newtie : Red Katte (Female)
Jr. Pepe : Jr. Pepe Fish
Miner Zombie : Zombie Lost Tooth
Miner Zombie : Zombie Lost Gold Tooth
Miner Zombie : Minor Zombie Card
Miner Zombie : Ascalon Katara
Aufheben : Aufheben Time Sand
Aufheben : Aufheben Card
Eye of Time : Scroll for Overall Armor for STR 60%
Eye of Time : Scroll for Knuckler for Accuracy 60%
Eye of Time : Heaven Gate
Psycho Jack : Fireman Axe
Psycho Jack : Red Hunter Armor
Psycho Jack : Red Hunter Pants
Psycho Jack : Red White-Line Boots
Death Teddy : Blazing Dragon Katara
Bain : Bain Spiky Collar
Bain : Gold Arund
Dark Nependeath : Equip Enhancement Scroll
Dark Nependeath : Dark Piette Pants
Timu : Timu Feather
Mano : Rainbow Colored Snail Shell
Dark Stone Golem : Dark Stone Golem Card
Scorpion : Cat Eye Stone
King Clang : Lorang Claws
King Clang : Clang Claws
Nependeath : Nependeath Honey
Phantom Watch : Dragon Tail
Phantom Watch : Dark Pirate Bottom
Reinforced Iron Mutae : Reinforced Iron Mutae Card
Master Chronos : Brown Jangoon Boots
Crimson Balrog the Kidnapper : [Skill Book] Dragon Breath
Lazy Buffy : Heavenly Katara
Red Slime : Kinoko Ramen (Salt)
Evil Eye : Cat Eye
Maverick Type D : Maverick Type D Card
Maverick Type D : Green Battle Lord
Maverick Type A : Maverick Type A Card
Maverick Type A : Infinity Wrath
Klock : Heavenly Katara
Tiguru : Tiguru Feather
Tortie : Lion Fang
Shade : Someone Hat
Dunas : Dunas Time Sand
Dunas : Dunas Card
Timer : Scroll for Knuckler for Accuracy 60%
Timer : Serpent Coil
Timer : Serpent Coil
Maverick Type S : Maverick Type S Card
Poison Poopa : Poison Poopa Poisonous Spikes
Poison Poopa : Blue White-Line Boots
Murupia : Murupia Furball
Chief Gray : Chief Gray Sign
Chief Gray : Chief Gray Card
Chief Gray : Dark Pennance
Skelosaurus : Meteor Katara
Oberon : Oberon Time Sand
Oberon : Oberon Card
Mr. Alli : Mr. Alli Leather
Mr. Alli : Lion Fang
Cellion : Maroon Jangoon Boots
Qualm Monk Trainee : [Mastery Book] Blessing of the Onyx 20
Chronos : Chronos Egg
Kiyo : Kiyo Beak
Cherry Bubble Tea : Cherry Bubble Tea Card
Cherry Bubble Tea : Maroon Jangoon Boots
Pink Teddy : Teddy Yellow Ribbon
Pink Teddy : White/Purple Gift Box
Rexton : [Mastery Book] Spirit Claw
Master of Disguise : The Lost Treasure [1]
Master of Disguise : The Lost Treasure [2]
Master of Disguise : The Lost Treasure [3]
Master of Disguise : The Lost Treasure [4]
Master of Disguise : The Lost Treasure [5]
Neo Huroid : Gold Mask
Jr. Pepe Doll : Guardian Katara
Dual Birk : Dual Birk Tiny Tail
Muru : Muru Furball
Grey Yeti and King Pepe : King Pepe Scroll for One-handed BW Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Dagger Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Two-handed Sword Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Two-handed Axe Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Two-handed BW Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Spear Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Polearm Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Bow Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Crossbow Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Wand Magic Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Staff Magic Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Thief Attacks 60%
Grey Yeti and King Pepe : King Pepe Scroll for Knuckle Attacks 60%
Wild Kargo : Gold Wings
Bubbling : Bubbling Large Bubble
Bubbling : Blue Jewelry Shoes
Bubbling : Champion Katara
Papulatus : [Mastery Book] High Mastery 30
Papulatus : Red Katte (Female)
Papulatus : Red Katinas (Male)
Plateon : Plateon Helmet
Plateon : Serpent Tongue
Nightghost : Ghost Headband
Nightghost : Serpent Coil
Nightghost : Serpent Coil
Mango Bubble Tea : Mango Bubble Tea Card
Mango Bubble Tea : Maroon Jangoon Boots
Homunculus : Homunculus Card
Homunculus : Green Pirate Bottom
Homunculus : Brown Stud
Homunculus : Brown Stud Pants
Orange Mushroom : Hunter Bow
Orange Mushroom : Basic Archer Glove
Scarf Plead : Worn-Out Muffler
Scarf Plead : Green Hunter Armor
Scarf Plead : Green Hunter Pants
Female Boss : Lady Boss Comb
Female Boss : Devil Sunrise
Hogul : Hogul Card
Memory Guardian : Meteor Katara
Poison Mushroom : King Pepe Scroll for Two-handed Sword Attacks 60%
Poison Mushroom : King Pepe Scroll for Two-handed Axe Attacks 60%
Poison Mushroom : Justice Katara
Homun : Gold Pendant
Homun : Ascalon Katara
Greatest Oldies : Dark Scroll for Accessory for STR 70%
Greatest Oldies : Greatest Oldies Card
Kid Mannequin : Kid Mannequin Bunny Suit
Kid Mannequin : Dark Scroll for Accessory for STR 70%
Kid Mannequin : Kid Mannequin Card
Kid Mannequin : Justice Katara
Dark Rash : Dark Rash Furball
Cerebes : Beige Elf Shoes
Grizzly : Equip Enhancement Scroll
Lioner : Gold Tail
Lioner : Majestic Katara
Red Wyvern : Equip Enhancement Scroll
Gigantic Spirit Viking : Gigantic Spirit Viking Card
Gigantic Spirit Viking : Heaven Gate
Gigantic Spirit Viking : Red Battle Lord
Gigantic Spirit Viking : Beige Elf Shoes
Hector : Blue Jangoon Boots
Hector : Serpent Coil
Hector : Serpent Coil
Crimson Balrog : [Storybook] Crimson Balrog Proposal
Memory Monk : [Mastery Book] High Mastery 30
Lupin : Lupin Banana
Tae Roon : Mind And Heart Medicine
Birk : Birk Chewed Grass
Birk : Dark Battle Lord
Birk : Blazing Dragon Katara
Clang : Clang Claws
Clang : Lion Fang
Straw Target Dummy : Straw Target Dummy Card
Royal Guard Pepe : King Pepe Scroll for Crossbow Attacks 60%
Royal Guard Pepe : King Pepe Scroll for Thief Attacks 60%
Royal Guard Pepe : King Pepe Scroll for Knuckle Attacks 60%
Blue King Goblin : Blue Stud
Nightmare : Blue Jangoon Boots
Renegade Spores : King Pepe Scroll for One-handed BW Attacks 60%
Renegade Spores : King Pepe Scroll for Dagger Attacks 60%
Renegade Spores : Potential Scroll
Prime Minister : King Pepe Scroll for One-handed BW Attacks 60%
Prime Minister : King Pepe Scroll for Dagger Attacks 60%
Prime Minister : King Pepe Scroll for Two-handed Sword Attacks 60%
Prime Minister : King Pepe Scroll for Two-handed Axe Attacks 60%
Prime Minister : King Pepe Scroll for Two-handed BW Attacks 60%
Prime Minister : King Pepe Scroll for Spear Attacks 60%
Prime Minister : King Pepe Scroll for Polearm Attacks 60%
Prime Minister : King Pepe Scroll for Wand Magic Attacks 60%
Prime Minister : King Pepe Scroll for Staff Magic Attacks 60%
Prime Minister : King Pepe Scroll for Bow Attacks 60%
Prime Minister : King Pepe Scroll for Crossbow Attacks 60%
Prime Minister : King Pepe Scroll for Thief Attacks 60%
Prime Minister : King Pepe Scroll for Knuckle Attacks 60%
Prime Minister : Lion Fang
Prime Minister : Serpent Tongue
Prime Minister : Justice Katara
Prime Minister : Guardian Katara
Ear Plug Plead : Ear Muffs
Oly Oly : Somebody Tire
Samiho : Samiho Card
Charmer : Charmer Flute
Murupa : Murupa Furball
Murukun : Murukun Furball
Bob : Bob Snail Shell
Horny Mushroom : Guardian Katara
Brown Teddy : Teddy Cotton
Brown Teddy : Equip Enhancement Scroll
Brown Teddy : Guardian Katara
Thanatos : Thanatos' Strap
Thanatos : [Storybook] Black Book
Thanatos : Dark Battle Lord
Mummydog : Mummydog Card
Dark Cornian : Devil Sunrise
Dark Cornian : Red Katinas (Male)
Intoxicated Pig : King Pepe Scroll for Two-handed BW Attacks 60%
Intoxicated Pig : King Pepe Scroll for Spear Attacks 60%
Red Flower Serpent : Red Flower Serpent Card
Red Flower Serpent : Gold Mask
Propelly : Lion Fang
Propelly : Fireman Axe
Propelly : Dark Pennance
Platoon Chronos : Equip Enhancement Scroll
Platoon Chronos : Serpent Tongue
Chirppy : Fireman Axe
Chirppy : Red Hunter Armor
Chirppy : Red White-Line Boots
Planey : Brown Jangoon Boots
Zombie Mushmom : Zombie Mushmom Card
Dark Leatty : Black Guise
Dark Leatty : Green White-Line Boots
Ligator : Equip Enhancement Scroll
Male Boss : Boss Pomade
Cloud Fox : Sushi (Salmon)
Cloud Fox : Fish Cake (Skewer)
Cloud Fox : Cat Eye
Bodyguard B : Bodyguard B Bullet Shell
Baby Typhon : Dark Scroll for Cape for Weapon Def 70%
Bodyguard A : Bodyguard A Tie Pin
Ergoth : Ergoth Jawbone
Annoyed Zombie Mushroom : The Charm of the Undead
Annoyed Zombie Mushroom : Hunter Bow
Cico : Seahorse Horn
Robo : Blue Jangoon Boots
Wraith : Cat Eye
Wraith : Gold Pendant
Red Drake : Dark Piette Pants
Killa Bee : Cat Eye
Skeledog : Skeledog Bone
Skeledog : Maroon Jangoon Boots
Windraider : Scroll for Gloves for DEX 15%
Tutorial Muru : Tutorial Muru Furball

View File

@@ -1,6 +0,0 @@
/* Manually run this script in MySQL Workbench or some other database client
to migrate your old (pre Jan 19th 2022) monsterbook table to the new version */
ALTER TABLE cosmic.`monsterbook`
CHANGE COLUMN `charid` `charid` INT(11) NOT NULL,
ADD PRIMARY KEY (`charid`, `cardid`),
ADD CONSTRAINT `FK_monsterbook_1` FOREIGN KEY (`charid`) REFERENCES `characters` (`id`) ON UPDATE CASCADE ON DELETE CASCADE;

View File

@@ -1,7 +0,0 @@
---- Cosmic MySQL Database ----
These SQL files must be executed IN ORDER to set up the database:
- 1-db_database.sql (Tables and some data)
- 2-db_drops.sql (Remaining data: monster drops, reactor drops)
- 3-db_shopupdate.sql (Custom shops - optional, requires provided WZs)
- 4-db_admin (Basic admin account - optional)

View File

@@ -4,6 +4,7 @@ services:
build: .
depends_on:
- db
- pg_db
ports:
# Login server
- "8484:8484"
@@ -19,16 +20,25 @@ services:
- ./wz:/opt/server/wz
environment:
DB_HOST: "db" ## Remember if this is present it will OVERRIDE the host in the config.yaml, if you put here anything other than db, you'll need to change the config.yaml jdbc string to port 3307, and not port 3306
PG_DB_HOST: "pg_db"
db:
image: mysql:8.0.23
image: mysql:8.4.0
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "true"
MYSQL_DATABASE: "cosmic"
MYSQL_USER: "cosmic_server"
MYSQL_PASSWORD: "snailshell"
MYSQL_ROOT_PASSWORD: ""
MYSQL_ALLOW_EMPTY_PASSWORD: yes
ports:
- "3307:3306"
volumes:
- ./database/docker-db-data:/var/lib/mysql
- ./database/sql:/docker-entrypoint-initdb.d
pg_db:
image: postgres:15.0
ports:
- "5433:5432"
environment:
POSTGRES_PASSWORD: postgres
volumes:
- ./database/docker-pg-db-data:/var/lib/postgresql/data
- ./database/postgres-scripts:/docker-entrypoint-initdb.d

View File

@@ -302,10 +302,10 @@ Localhost:
* Removed caps for MATK, WDEF, MDEF, ACC and AVOID.
* Removed "AP excess" popup and "Admin/MWLB" action block, original credits to kevintjuh93.
* Removed "You've gained a level!" popup, original credits to PrinceReborn.
* Removed "Cannot enter MTS from this map." popup on maps that blocks transitions (such change channel, CS/MTS), rendering the buyback option now available for all maps.
* Removed "Cannot enter MTS from this map." popup on maps that blocks transitions (such change channel, CS/MTS).
* Removed a check for players wishing to create/join a party being novices under level 10.
* Set a new high cap for SPEED.
* Removed the AP assign block for novices.
* Removed a block that would show up when trying to apply an attack gem on equipments that aren't weapons.
---------------------------
---------------------------

View File

@@ -480,9 +480,7 @@
5590000 - High-Five Stamp - Can equip items that are 5 levels above your current level.
5021026 - Gift Box Throwing Stars - A gift box that can be freely thrown around. Using the #cThrowing Star# will create an orbital effect.\n\nThis item cannot be deleted.
5010073 - Miss Popular - Well, lookie here. Someone<6E>s certainly become popular with the guys. Turn this effect on and off by assigning it to a shortcut key from the keyboard settings menu.
5010074 - Mr. Popular - Well, lookie here. Someone<6E>s certainly become popular with the girls. Turn this effect on and off by assigning it to a shortcut key from the keyboard settings menu.
5240027 - Golden Drumstick - A drumstick that can be consumed only by #cBaby Tiger#. It recovers hunger and #cincreases Closeness by 100.#
5390005 - Cute Tiger Messenger - Shout to everyone in the world your character is on with this megaphone. Now available with your avatar on the top of everyone's screen! Comes with a tiger background for your avatar.
5390006 - Roaring Tiger Messenger - Shout to everyone in the world your character is on with this megaphone. Now available with your avatar on the top of everyone's screen! Comes with a screen shaked.

200
handbook/Commands.txt Normal file
View File

@@ -0,0 +1,200 @@
Common Commands:
@help - Show available commands.
@commands - Show available commands.
@droplimit - Check drop limit of current map.
@time - Show current server time.
@credits - Show credits. These people made the server possible.
@uptime - Show server online time.
@gacha - Show gachapon rewards.
@dispose - Dispose to fix NPC chat.
@changel - Change language settings
@equiplv - Show levels of all equipped items.
@showrates - Show all world/character rates.
@rates - Show your rates.
@online - Show all online players.
@gm- Send a message to the game masters.
@reportbug - Send in a bug report.
@points - Show point total.
@joinevent - Join active event.
@leaveevent-Leave active event.
@ranks - Show player rankings.
@str - Assign AP into STR.
@dex - Assign AP into DEX.
@int - Assign AP into INT.
@luk - Assign AP into LUK.
@enableauth - Enable PIC code by resetting the cooldown.
@toggleexp - Toggle enable/disable all exp gain.
@mylawn - Claim ownership of the current map.
@bosshp - Show HP of bosses on current map.
@mobhp - Show HP of mobs on current map.
Donator Commands:
@whatdropsfrom - Show what items drop from a mob.
@whodrops - Show what drops an item.
@buffme - Activate GM buffs on self.
@goto - Warp to a predefined map.
JrGM Commands:
!recharge - Recharge and refill all USE items.
!whereami - Show info about objects on current map.
!hide - Hide from players.
!unhide - Toggle Hide.
!sp - Set available SP.
!ap - Set available AP.
!empowerme - Activate all useful buffs.
!buffmap - Give GM buffs to the whole map.
!buff - Activate a buff.
!bomb - Bomb a player, dealing damage.
!dc - Disconnect a player.
!cleardrops - Clear drops by player.
!clearslot - Clear all items in an inventory tab.
!clearsavelocs - Clear saved locations for a player.
!warp - Warp to a map.
!warphere - Move a player to your location.
!summon - Move a player to your location.
!warpto - Warp to a player.
!reach - Warp to a player.
!follow - Warp to a player.
!gmshop - Open the GM shop.
!heal - Fully heal your HP/MP.
!item - Spawn an item into your inventory.
!drop - Spawn an item onto the ground.
!level - Set your level.
!levelpro - Set your level, one by one.
!setslot - Set amount of inventory slots in all tabs.
!setstat - Set all primary stats to new value.
!maxstat - Max out all character stats.
!maxskill - Max out all job skills.
!resetskill Set all skill levels to 0.
!search - Search String.wz.
!jail - Move a player to the jail.
!unjail- Free a player from jail.
!job - Change job of a player.
!unbug - Unbug self.
!id - Search in handbook.
!gachalist - Show gachapon rewards.
!loot - Loots all items that belong to you.
!mobskill - Apply a mob skill to all mobs on the map.
Args: <mob skill id> <skill level>
GM Commands:
!debuff - Put a debuff on all nearby players.
!fly - Enable/disable fly feature.
!spawn - Spawn mob(s) on your location.
!mutemap - Toggle mute players in the map.
!checkdmg - Show stats and damage of a player.
!inmap - Show all players in the map.
!reloadevents - Reload all event data.
!reloaddrops - Reload all drop data.
!reloadportals - Reload all portal scripts.
!reloadmap - Reload the map.
!reloadshops - Reload popup shops and NPC shops.
!hpmp - Set HP/MP of a player.
!maxhpmp - Set base HP/MP of a player.
!music - Play a song.
!monitor - Toggle monitored packet logging of a character.
!monitors - Show all characters being monitored for packet logging.
!ignore - Toggle ignore a character from auto-ban alerts.
!ignored - Show all characters being ignored in auto-ban alerts.
!pos - Show current position and foothold.
!togglecoupon - Toggle enable/disable a coupon.
!togglewhitechat - Toggle white GM chat.
!fame - Set new fame value of a player.
!givenx - Give NX to a player.
!givevp - Give vote points to a player.
!givems - Give mesos to a player.
!giverp - Give reward points to a player.
!expeds - Show all ongoing boss expeditions.
!kill - Kill a player.
!seed - Drop all seeds inside Henesys PQ.
!maxenergy - Set dojo energy to max value.
!killall - Kill all mobs in the map.
!notice - Send a blue message to everyone on the server.
!rip - Send a RIP notice.
!openportal - Open a portal on the map.
!closeportal - Close a portal.
!pe - Handle synthesized packets from file, and handle them as if sent from a client.
!startevent - Start an event on current map.
!endevent - Close entry for ongoing event.
!startmapevent - Start a "classic" event on current map.
!stopmapevent - Stop ongoing "classic" event.
!online2 - Show all online players.
!ban - Ban a player.
!unban - Unban a player.
!healmap - Heal all HP/MP of all players in the map.
!healperson - Heal all HP/MP of a player.
!hurt - Nearly kill a player.
!killmap - Kill all players in the map.
!night - Set sky background to black.
!npc - Spawn an NPC on your location.
!face - Change face of a player.
!hair - Change hair of a player.
!startquest - Start a quest.
!completequest - Complete an active quest.
!resetquest - Reset a completed quest.
!timer - Set timer on a player in current map.
!timermap - Set timer on all players in current map.
!timerall Set a server wide timer.
!warpmap - Warp all characters on current map to a new map.
!warparea - Warp all nearby players to a new map.
SuperGM Commands:
!servermessage - Set scrolling server message.
!proitem - Spawn an item with custom stats.
!seteqstat-Set stats of all equips in inventory.
!exprate - Set world exp rate.
!mesorate - Set world meso rate.
!droprate - Set world drop rate.
!bossdroprate - Set world boss drop rate.
!questrate - Set world quest rate.
!travelrate - Set world travel rate.
!fishrate - Set fishing rate.
!itemvac - Loot all drops on the map.
!forcevac-Loot all drops on the map.
!zakum - Spawn Zakum on your location.
!horntail - Spawn Horntail on your location.
!pinkbean - Spawn Pink Bean on your location.
!pap - Spawn Papulatus on your location.
!pianus - Spawn Pianus (R) on your location.
!cake - Spawn Cake boss with specified HP.
!playernpc - Spawn a player NPC of an online player.
!playernpcremove - Remove a "Iv 200" player NPC.
!pnpc - Spawn a permanent NPC on your location.
!pnpcremove - Remove a permanent NPC on the map.
!pmob - Spawn a permanent mob on your location.
!pmobremove - Remove all permanent mobs of the same type on the map.
Developer Commands:
!debug - Show a debug message.
!set - Store value in an array, for testing.
!showpackets - Toggle show received packets in console.
!showmovelife - Toggle show move life in console.
!showsessions - Show online sessions.
!iplist - Show IP of all players.
Admin Commands:
!setgmlevel - Set GM level of a player.
!warpworld - Warp to a different world.
!saveall - Save all characters.
!dcall - Disconnect all players (online or logged in).
!mapplayers - Show all players on the map.
!getacc - Show account name of an online player.
!shutdown - Shut down the server.
!clearquestcache - Clear all quest cache.
!clearquest - Clear cache of a quest.
!supplyratecoupon - Set availability of coupons in Cash Shop.
!spawnallpnpcs - Spawn player NPCs of all existing players.
!eraseallpnpcs - Remove all player NPCs.
!addchannel - Add a new channel to a world.
!addworld - Add a new world.
!removechannel - Remove channel from a world.
!removeworld - Remove a world.
!devtest - Runs devtest.js. Developer utility - test stuff without restarting the server.

9
handbook/Equip/Skin.txt Normal file
View File

@@ -0,0 +1,9 @@
0 - Light - (no description)
1 - Tanned - (no description)
2 - Dark - (no description)
3 - Pale - (no description)
4 - Blue - (no description)
5 - Green - (no description)
9 - White - (no description)
10 - Pink - (no description)
11 - Brown - (no description)

2
handbook/Gender.txt Normal file
View File

@@ -0,0 +1,2 @@
0 - Male - (no description)
1 - Female - (no description)

70
handbook/Job.txt Normal file
View File

@@ -0,0 +1,70 @@
0 - Beginner - (no description)
100 - Warrior - (no description)
110 - Fighter - (no description)
111 - Crusader - (no description)
112 - Hero - (no description)
120 - Page - (no description)
121 - White Knight - (no description)
122 - Paladin - (no description)
130 - Spearman - (no description)
131 - Dragon Knight - (no description)
132 - Dark Knight - (no description)
200 - Magician - (no description)
210 - Fp Wizard - (no description)
211 - Fp Mage - (no description)
212 - Fp Archmage - (no description)
220 - Il Wizard - (no description)
221 - Il Mage - (no description)
222 - Il Archmage - (no description)
230 - Cleric - (no description)
231 - Priest - (no description)
232 - Bishop - (no description)
300 - Bowman - (no description)
310 - Hunter - (no description)
311 - Ranger - (no description)
312 - Bow Master - (no description)
320 - Crossbowman - (no description)
321 - Sniper - (no description)
322 - Crossbow Master - (no description)
400 - Thief - (no description)
410 - Assassin - (no description)
411 - Hermit - (no description)
412 - Night Lord - (no description)
420 - Bandit - (no description)
421 - Chief Bandit - (no description)
422 - Shadower - (no description)
500 - Pirate - (no description)
510 - Brawler - (no description)
511 - Marauder - (no description)
512 - Buccaneer - (no description)
520 - Gunslinger - (no description)
521 - Outlaw - (no description)
522 - Corsair - (no description)
800 - Maple Leaf Brigadier - (no description)
900 - Gm - (no description)
910 - Super Gm - (no description)
1000 - Noblesse - (no description)
1100 - Dawn Warrior 1 - (no description)
1110 - Dawn Warrior 2 - (no description)
1111 - Dawn Warrior 3 - (no description)
1200 - Blaze Wizard 1 - (no description)
1210 - Blaze Wizard 2 - (no description)
1211 - Blaze Wizard 3 - (no description)
1212 - Blaze Wizard 4 - (no description)
1300 - Wind Archer 1 - (no description)
1310 - Wind Archer 2 - (no description)
1311 - Wind Archer 3 - (no description)
1312 - Wind Archer 4 - (no description)
1400 - Night Walker 1 - (no description)
1410 - Night Walker 2 - (no description)
1411 - Night Walker 3 - (no description)
1412 - Night Walker 4 - (no description)
1500 - Thunder Breaker 1 - (no description)
1510 - Thunder Breaker 2 - (no description)
1511 - Thunder Breaker 3 - (no description)
1512 - Thunder Breaker 4 - (no description)
2000 - Legend - (no description)
2100 - Aran 1 - (no description)
2110 - Aran 2 - (no description)
2111 - Aran 3 - (no description)
2112 - Aran 4 - (no description)

View File

@@ -36,10 +36,7 @@
5000044 - Orange tiger - They are very gentle and obedient in nature, easily becoming friendly with the owner, which in turn speeds up the level-up process.
5000045 - Skunk - Calm and quiet by nature, the easy-going, eccentric Skunk has a rather humorous and pompous outlook on life, the finer things are what it enjoys.
5000026 - Sun Wu Kong - Sun Wu Kong ran out of Tang Shan Zhang. He is lazy and moody but can be changed with love.
5000040 - ??? ?? - ??? ??? ??? ??? ???????. ??? ????? ??? ?????, ??? ? ?? ??? ??? ???? ??? ?? ????. \n#c??: ?? ??, ??? ??, ??? ?? ???&?? ??, ?? ??, ???? ??, MP ????#
5000042 - Kino - A happy-go-lucky, orange mushroom that loves shiny stones.
5000043 - ???? - ??? ????, ?? ??? ??? ???????. ??? ?? ? ????, ??? ??? ???? ??? ?? ????. \n#c??: ????, ?????#
5000046 - ?? - ???? ?? ?? ???? ???? ??? ???????. \n#c??:????, ?????#
5000047 - Robo - A special egg that becomes a mythical Robo upon hatching.
5000048 - Baby Robo - A Baby Robo hatched out of the special Capsule. At Level 15, the pet can be evolved into a full-fledged Adult Robo with the help of Garnox the NPC using the Rock of Evolution.
5000049 - Blue Robo - Mysterious magical powers aligned and sucessfully evolved the Baby Robo into a Blue Robo!

View File

@@ -385,7 +385,6 @@
2030016 - Phyllia's Warp Powder - Warp powder made by fairy Phyllia. Teleports you to Magatia when used inside the Nihal desert region.
2030019 - Return Scroll to Nautilus - This scroll enables you to return to the Pirate village, Nautilus. This is a one use item and will disappear after use.
2030020 - Return Scroll to New Leaf City - Use this scroll to venture back to New Leaf City whenever you want!
2030100 - Return Scroll - Banished Area - Returns you to the map where you were last banished. Requires immediate use and have changed neither maps nor channels.
2031000 - Masked Man's Invitation - An invitation from the Masked Man to the Halloween Party at the Haunted Mansion. Double-click to move straight to the mansion.
2031001 - Studio Invitation - An invitation to the studio for the event "For Guild Only".
2040000 - Scroll for Helmet for DEF - Improves the helmet's weapon def.\nSuccess rate:100%, weapon def. +1

259
mvnw vendored Normal file
View File

@@ -0,0 +1,259 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

149
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,149 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
if ($env:MAVEN_USER_HOME) {
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
}
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

133
pom.xml
View File

@@ -1,40 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cosmic-maplestory</groupId>
<artifactId>Cosmic</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Cosmic</name>
<description>Server emulator for Global MapleStory version 83</description>
<url>https://github.com/P0nk/Cosmic</url>
<inceptionYear>2021</inceptionYear>
<developers>
<developer>
<name>Ponk</name>
<email>ponkcode@gmail.com</email>
<url>https://github.com/P0nk</url>
<roles>
<role>maintainer</role>
<role>developer</role>
</roles>
<properties>
<discord>ponkcode</discord>
</properties>
</developer>
</developers>
<scm>
<connection>scm:git:https://github.com/P0nk/Cosmic.git</connection>
<developerConnection>scm:git:https://github.com/P0nk/Cosmic.git</developerConnection>
<url>https://github.com/P0nk/Cosmic</url>
</scm>
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/P0nk/Cosmic/issues</url>
</issueManagement>
<ciManagement>
<system>GitHub Actions</system>
<url>https://github.com/P0nk/Cosmic/actions</url>
</ciManagement>
<properties>
<!-- Project -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>17</java.version>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<mainClass>net.server.Server</mainClass>
<!-- Maven plugins -->
<maven-surefire-plugin.version>3.0.0-M9</maven-surefire-plugin.version> <!-- For running unit tests -->
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version> <!-- Disabled. (for building thin jar) -->
<maven-assembly-plugin.version>3.5.0</maven-assembly-plugin.version> <!-- For packaging the executable fat jar -->
<maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version> <!-- For running unit tests -->
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version> <!-- Disabled. (for building thin jar) -->
<maven-assembly-plugin.version>3.7.1
</maven-assembly-plugin.version> <!-- For packaging the executable fat jar -->
<!-- Dependencies -->
<slf4j-api.version>1.7.36</slf4j-api.version> <!-- Logging facade -->
<log4j.version>2.20.0</log4j.version> <!-- Slf4j implementation -->
<graalvm.version>22.3.1</graalvm.version> <!-- ScriptEngine implementation -->
<netty.version>4.1.89.Final</netty.version> <!-- Networking -->
<yamlbeans.version>1.15</yamlbeans.version> <!-- Config file -->
<slf4j-api.version>2.0.17</slf4j-api.version> <!-- Logging facade -->
<log4j.version>2.25.0</log4j.version> <!-- Slf4j implementation -->
<graalvm-js.version>23.0.4</graalvm-js.version>
<graalvm-js-scriptengine.version>24.0.1</graalvm-js-scriptengine.version> <!-- ScriptEngine implementation -->
<netty.version>4.2.2.Final</netty.version> <!-- Networking -->
<yamlbeans.version>1.17</yamlbeans.version> <!-- Config file -->
<jcip-annotations.version>1.0</jcip-annotations.version> <!-- Annotations for concurrency documentation -->
<HikariCP.version>5.0.1</HikariCP.version> <!-- Database connection pool -->
<mysql-connector-j.version>8.0.32</mysql-connector-j.version> <!-- MySQL JDBC driver -->
<jdbi-version>3.37.1</jdbi-version> <!-- Convenience wrapper around JDBC -->
<junit.version>5.9.2</junit.version> <!-- Unit test -->
<mockito.version>5.1.1</mockito.version> <!-- Unit test -->
<HikariCP.version>6.3.0</HikariCP.version> <!-- Database connection pool -->
<mysql-connector-j.version>9.3.0</mysql-connector-j.version> <!-- MySQL JDBC driver -->
<jdbi-version>3.49.5</jdbi-version> <!-- Convenience wrapper around JDBC -->
<liquibase-core.version>4.32.0</liquibase-core.version> <!-- Database migrations -->
<junit.version>5.13.1</junit.version> <!-- Unit test -->
<mockito.version>5.18.0</mockito.version> <!-- Unit test -->
<testcontainers.version>1.20.1</testcontainers.version> <!-- Docker test with real temporary database -->
<postgresql.version>42.5.4</postgresql.version> <!-- PostgreSQL JDBC driver -->
<flyway.version>9.15.1</flyway.version> <!-- Database migration -->
<caffeine.version>3.1.4</caffeine.version> <!-- Caching -->
<lombok.version>1.18.34</lombok.version> <!-- Code generation -->
</properties>
<dependencies>
@@ -48,6 +87,17 @@
<artifactId>jcip-annotations</artifactId>
<version>${jcip-annotations.version}</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Database -->
<dependency>
@@ -60,12 +110,26 @@
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector-j.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-core</artifactId>
<version>${jdbi-version}</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>${flyway.version}</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>${liquibase-core.version}</version>
</dependency>
<!-- Networking -->
<dependency>
@@ -107,7 +171,7 @@
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>${log4j.version}</version>
</dependency>
@@ -115,12 +179,12 @@
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>${graalvm.version}</version>
<version>${graalvm-js.version}</version>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId>
<version>${graalvm.version}</version>
<version>${graalvm-js-scriptengine.version}</version>
</dependency>
<!-- Testing -->
@@ -148,6 +212,31 @@
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

3
scripts/devtest.js Normal file
View File

@@ -0,0 +1,3 @@
function run(chr) {
chr.message("devtest.js")
}

View File

@@ -36,12 +36,6 @@ function stopEntry() {
}
function takeoff() {
const PacketCreator = Java.type('tools.PacketCreator');
//sound src: https://www.soundjay.com/transportation/metro-door-close-01.mp3
KC_docked.broadcastMessage(PacketCreator.playSound("subway/whistle"));
NLC_docked.broadcastMessage(PacketCreator.playSound("subway/whistle"));
em.setProperty("docked", "false");
KC_Waiting.warpEveryone(Subway_to_NLC.getId());
NLC_Waiting.warpEveryone(Subway_to_KC.getId());
@@ -52,10 +46,6 @@ function arrived() {
Subway_to_KC.warpEveryone(KC_docked.getId(), 0);
Subway_to_NLC.warpEveryone(NLC_docked.getId(), 0);
scheduleNew();
const PacketCreator = Java.type('tools.PacketCreator');
KC_docked.broadcastMessage(PacketCreator.playSound("subway/whistle"));
NLC_docked.broadcastMessage(PacketCreator.playSound("subway/whistle"));
}
function cancelSchedule() {}

View File

@@ -31,7 +31,7 @@ function action(mode, type, selection) {
}
if (status == 0) {
cm.sendSimple("Hey, you look like you need a breather. You should be enjoying the life, just like I am. Well, if you have a couple of items, I can trade you for an item you can play minigames with. Now... what can I do for you?#b\r\n#L0#Create a minigame item#l\r\n#L1#Explain to me what the minigames are about#l#k");
cm.sendSimple("Hey, you look like you need a breather from all that hunting. You should be enjoying the life, just like I am. Well, if you have a couple of items, I can trade you for an item you can play minigames with. Now... what can I do for you?#b\r\n#L0#Create a minigame item#l\r\n#L1#Explain to me what the minigames are about#l#k");
} else if (status == 1) {
if (selection == 0) {
@@ -57,6 +57,7 @@ function action(mode, type, selection) {
if (cm.haveItem(4030012, 15)) {
cm.gainItem(4030012, -15);
cm.gainItem(4080100, 1);
cm.dispose();
} else {
cm.sendNext("You want #bA set of Match Cards#k? Hmm...to make A set of Match Cards, you'll need some #bMonster Cards#k. Monster Card can be obtained by taking out the monsters all around the island. Collect 15 Monster Cards and you can make a set of A set of Match Cards."); //Lmfao a set of A set xD
cm.dispose();
@@ -81,7 +82,7 @@ function action(mode, type, selection) {
if (current == 1 || current == 2) {
cm.sendNextPrev("Enter the room, and when you're ready to play, click on #bReady#k.\r\nOnce the visitor clicks on #bReady#k, the room owner can press #bStart#k to begin the game. If an unwanted visitor walks in, and you don't want to play with that person, the room owner has the right to kick the visitor out of the room. There will be a square box with x written on the right of that person. Click on that for a cold goodbye, okay?"); //Oh yeah, because people WALK in Omok Rooms.
} else if (current == 3) {
if (cm.haveItem(omok1piece[selection], 99) && cm.haveItem(omok2piece[selection], 99) && cm.haveItem(4030009, 1)) {
if (cm.haveItem(omok1piece[selection], omokamount) && cm.haveItem(omok2piece[selection], omokamount) && cm.haveItem(4030009, 1)) {
cm.gainItem(omok1piece[selection], -omokamount);
cm.gainItem(omok2piece[selection], -omokamount);
cm.gainItem(4030009, -1);
@@ -95,7 +96,7 @@ function action(mode, type, selection) {
} else if (status == 5) {
if (current == 1) {
cm.sendNextPrev("When the first fame starts, #bthe room owner goes first#k. Beward that you'll be given a time limit, and you may lose your turn if you don't make your move on time. Normally, 3 x 3 is not allowed, but if there comes a point that it's absolutely necessary to put your piece there or face ending the game, then you can put it there. 3 x 3 is allowed as the last line of defense! Oh, and it won't count if it's #r6 or 7 straight#k. Only 5!");
cm.sendNextPrev("When the first game starts, #bthe room owner goes first#k. Beward that you'll be given a time limit, and you may lose your turn if you don't make your move on time. Normally, 3 x 3 is not allowed, but if there comes a point that it's absolutely necessary to put your piece there or face ending the game, then you can put it there. 3 x 3 is allowed as the last line of defense! Oh, and it won't count if it's #r6 or 7 straight#k. Only 5!");
} else if (current == 2) {
cm.sendNextPrev("Oh, and unlike Omok, when you create the game room for Match Cards, you'll need to set your game on the number of cards you'll use for the game. There are 3 modes avaliable, 3x4, 4x5, and 5x6, which will require 12, 20, and 30 cards respectively. Remember that you won't beable to change it up once the room is open, so if you really wish to change it up, you may have to close the room and open another one.");
}
@@ -110,12 +111,14 @@ function action(mode, type, selection) {
} else if (status == 7) {
if (current == 1) {
cm.sendPrev("When the next game starts, the loser will go first. Also, no one is allowed to leave in the middle of a game. If you do, you may need to request either a #bforfeit or tie#k. (Of course, if you request a forfeit, you'll lose the game.) And if you click on 'Leave' in the middle of the game and call to leave after the game, you'll leave the room right after the game is over. This will be a much more useful way to leave.");
cm.dispose();
} else if (current == 2) {
cm.sendNextPrev("If you and your opponent have the same number of matched pairs, then whoever had a longer streak of matched pairs will win. If you ever feel the need to go to the bathroom, or take an extended break, you can request a #btie#k. The game will end in a tie if the opponent accepts the request. Tip: this may be a good way to keep your friendships in tact.");
}
} else if (status == 8) {
if (current == 2) {
cm.sendPrev("When the next game starts, the loser will go first. Also, no one is allowed to leave in the middle of a game. If you do, you may need to request either a #bforfeit or tie#k. (Of course, if you request a forfeit, you'll lose the game.) And if you click on 'Leave' in the middle of the game and call to leave after the game, you'll leave the room right after the game is over. This will be a much more useful way to leave.");
cm.dispose();
}
}
}

View File

@@ -1,222 +0,0 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Rooney
Map Name (Map ID)
Used to exchange VP for Maple Leaves, and Maple Leaves for rewards.
*/
var itemToUse = 4001126;
var chairs = [3010000, 3010001, 3010002, 3010003, 3010004, 3010005, 3010006, 3010007, 3010008, 3010009, 3010010, 3010011, 3010012, 3010013, 3010015, 3010016, 3010017, 3010018, 3010019, 3010022, 3010023, 3010024, 3010025, 3010026, 3010028, 3010040, 3010041, 3010043, 3010045, 3010046, 3010047, 3010057, 3010058, 3010060, 3010061, 3010062, 3010063, 3010064, 3010065, 3010066, 3010067, 3010069, 3010071, 3010072, 3010073, 3010080, 3010081, 3010082, 3010083, 3010084, 3010085, 3010097, 3010098, 3010099, 3010101, 3010106, 3010116, 3011000, 3012005, 3012010, 3012011];
var scrolls = [2040603, 2044503, 2041024, 2041025, 2044703, 2044603, 2043303, 2040807, 2040806, 2040006, 2040007, 2043103, 2043203, 2043003, 2040506, 2044403, 2040903, 2040709, 2040710, 2040711, 2044303, 2043803, 2040403, 2044103, 2044203, 2044003, 2043703];
var weapons = [1302020, 1302030, 1302033, 1302058, 1302064, 1302080, 1312032, 1322054, 1332025, 1332055, 1332056, 1372034, 1382009, 1382012, 1382039, 1402039, 1412011, 1412027, 1422014, 1422029, 1432012, 1432040, 1432046, 1442024, 1442030, 1442051, 1452016, 1452022, 1452045, 1462014, 1462019, 1462040, 1472030, 1472032, 1472055, 1482020, 1482021, 1482022, 1492020, 1492021, 1492022, 1092030, 1092045, 1092046, 1092047];
var nxAmount = 3000;
var chairAmount = 2;
var weaponAmount = 2;
var buffAmount = 2;
var hiredMerchantLength = 7;
var buff1ID = 2022273;
var buff2ID = 2022179;
var status;
var vp;
var choice;
function start() {
//vp = cm.getClient().getVotePoints();
//if(vp == null)
vp = 0;
status = -1;
action(1, 0, 0);
}
function action(mode, type, selection) {
if (mode < 0) {
cm.dispose();
} else {
if (mode == 1) {
status++;
} else {
status--;
}
if (status == 0 && mode == 1) {
if (cm.getPlayer().getLevel() < 20) {
cm.sendOk("Hello, I am the Vote Point exchanger for #rMapleSolaxia#k!\r\n\r\nI am sorry, but I can only exchange Vote Points for players #blevel 20 or over#k.");
cm.dispose();
return;
}
var outStr = "Hello, I am the Vote Point exchanger for #rMapleSolaxia#k!\r\n";
outStr += "You currently have #r#c" + itemToUse + "##k #t" + itemToUse + "# and #r" + vp + "#k Vote Points.#b\r\n\r\n";
outStr += "#L0#I would like to exchange my vote points for Maple Leaves#l\r\n";
outStr += "#L1#I would like to exchange 1 #t" + itemToUse + "# for " + nxAmount + " NX Cash#l\r\n";
outStr += "#L2#I would like to exchange 1 #t" + itemToUse + "# for " + chairAmount + " Random Chair" + (chairAmount > 1 ? "s" : "") + "#l\r\n";
outStr += "#L3#I would like to exchange 1 #t" + itemToUse + "# for " + weaponAmount + " Maple Weapons#l\r\n";
outStr += "#L4#I would like to exchange 1 #t" + itemToUse + "# for " + buffAmount + " #t" + buff1ID + "#s and " + buffAmount + " #t" + buff2ID + "#s#l\r\n";
outStr += "#L5#I would like to exchange 1 #t" + itemToUse + "# for a " + hiredMerchantLength + " Day Hired Merchant#l\r\n";
cm.sendSimple(outStr);
} else if (status == 1) {
choice = selection;
if (selection > 0) {
if (!cm.haveItem(itemToUse) && vp == 0) {
cm.sendOk("I'm sorry, but you don't have any #t" + itemToUse + " or Vote Points.");
cm.dispose();
return;
}
}
if (selection == 0) {
// Exchange VP for leaves
if (vp <= 0) {
cm.sendOk("I'm sorry, but you don't have any Vote Points to exchange!");
cm.dispose();
return;
}
cm.sendYesNo("Would you like to exchange " + vp + " Vote Point" + (vp > 0 ? "s" : "") + " for " + vp + " #t" + itemToUse + "# " + (vp > 0 ? "s" : "") + "?");
} else if (selection == 1) {
// Exchange 1 Leaf for Cash
cm.sendYesNo("Would you like to exchange 1 #t" + itemToUse + "# for " + nxAmount + " NX Cash?");
} else if (selection == 2) {
// Exchange 1 Leaf for Chair
cm.sendYesNo("Would you like to exchange 1 #t" + itemToUse + "# for " + chairAmount + " Random Chair" + (chairAmount > 1 ? "s" : "") + "?");
} else if (selection == 3) {
// Exchange 1 Leaf for Maple Weapons
cm.sendYesNo("Would you like to exchange 1 #t" + itemToUse + "# for " + weaponAmount + " Random Maple Weapons?");
} else if (selection == 4) {
// Exchange 1 Leaf for Apples/Cheese
cm.sendYesNo("Would you like to exchange 1 #t" + itemToUse + "# for " + buffAmount + " #t" + buff1ID + "# and #t" + buff2ID + "#?");
} else if (selection == 5) {
// Echange 1 Leaf for Merchant
cm.sendYesNo("Would you like to exchange 1 #t" + itemToUse + "# for a " + hiredMerchantLength + " Day Hired Merchant?");
} else {
cm.dispose();
}
} else if (status == 2) {
var useVP = false;
if (!cm.hasItem(itemToUse) && vp > 0) {
useVP = true;
}
const InventoryType = Java.type('client.inventory.InventoryType');
if (choice == 0) {
// VP Exchange
if (!cm.canHold(itemToUse)) {
cm.sendOk("It looks like you don't have enough space in your #rETC#k inventory to hold the #t" + itemToUse + "#" + (vp > 0 ? "s" : "") + ".");
cm.dispose();
return;
}
cm.getClient().useVotePoints(vp);
cm.gainItem(itemToUse, vp);
cm.dispose();
} else if (choice == 1) {
// Leaf for Cash
if (useVP) {
cm.getClient().useVotePoints(1);
} else {
cm.gainItem(itemToUse, -1);
}
cm.getPlayer().getCashShop().gainCash(1, nxAmount);
const PacketCreator = Java.type('tools.PacketCreator');
cm.getPlayer().sendPacket(PacketCreator.earnTitleMessage("You have earned " + nxAmount + " NX"));
cm.logLeaf(nxAmount + " NX");
cm.dispose();
} else if (choice == 2) {
if (!cm.getPlayer().getInventory(InventoryType.SETUP).isFull(chairAmount)) {
var chairStr = "";
for (var i = 0; i < chairAmount; i++) {
var chair = chairs[Math.floor(Math.random() * chairs.length)];
cm.gainItem(chair, 1, true);
chairStr += chair + " ";
}
if (useVP) {
cm.getClient().useVotePoints(1);
} else {
cm.gainItem(itemToUse, -1);
}
cm.logLeaf("Chair ID: " + chairStr);
cm.dispose();
} else {
cm.sendOk("Please make sure you have enough space to hold the items!");
}
} else if (choice == 3) {
if (!cm.getPlayer().getInventory(InventoryType.EQUIP).isFull(weaponAmount)) {
var weaponStr = "";
for (var i = 0; i < weaponAmount; i++) {
var weapon = weapons[Math.floor(Math.random() * weapons.length)];
cm.gainItem(weapon, 1, true, true);
weaponStr += weapon + " ";
}
if (useVP) {
cm.getClient().useVotePoints(1);
} else {
cm.gainItem(itemToUse, -1);
}
cm.logLeaf("Maple Weapon IDs: " + weaponStr);
cm.dispose();
} else {
cm.sendOk("Please make sure you have enough space to hold the items!");
}
} else if (choice == 4) {
if (!cm.getPlayer().getInventory(InventoryType.USE).isFull(2)) {
cm.gainItem(buff1ID, buffAmount, true);
cm.gainItem(buff2ID, buffAmount, true);
cm.gainItem(itemToUse, -1);
cm.logLeaf(buffAmount + " cheeses and apples");
cm.dispose();
} else {
cm.sendOk("Please make sure you have enough space to hold the items!");
}
} else if (choice == 5) {
if (!cm.haveItem(5030000, 1)) {
if (!cm.getPlayer().getInventory(InventoryType.CASH).isFull(1)) {
cm.gainItem(5030000, 1, false, true, 1000 * 60 * 60 * 24 * hiredMerchantLength);
if (useVP) {
cm.getClient().useVotePoints(1);
} else {
cm.gainItem(itemToUse, -1);
}
cm.logLeaf(hiredMerchantLength + " day hired merchant");
cm.dispose();
} else {
cm.sendOk("Please make sure you have enough space to hold these items!");
}
} else {
cm.sendOk("I can't give you a merchant if you already have one!");
}
}
} else {
cm.dispose();
}
}
}

View File

@@ -1,25 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function start() {
const ShopFactory = Java.type('server.ShopFactory');
ShopFactory.getInstance().getShop(11000).sendShop(cm.getClient());
cm.dispose();
}

View File

@@ -21,25 +21,13 @@ var cpqMaxLvl = 50;
var cpqMinAmt = 2;
var cpqMaxAmt = 6;
// Ronan's custom ore refiner NPC
var refineRocks = true; // enables moon rock, star rock
var refineCrystals = true; // enables common crystals
var refineSpecials = true; // enables lithium, special crystals
var feeMultiplier = 7.0;
function start() {
status = -1;
const YamlConfig = Java.type('config.YamlConfig');
if (!YamlConfig.config.server.USE_CPQ) {
if (YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
status = 0;
action(1, 0, 4);
} else {
cm.sendOk("The Monster Carnival is currently unavailable.");
cm.dispose();
}
cm.sendOk("The Monster Carnival is currently unavailable.");
cm.dispose();
return;
}
@@ -239,9 +227,6 @@ function action(mode, type, selection) {
} else {
if (status == 0) {
var talk = "What would you like to do? If you have never participate in the Monster Carnival, you will need to know a few things before participating! \r\n#b#L0# Go to the Monster Carnival 1.#l \r\n#L3# Go to the Monster Carnival 2.#l \r\n#L1# Learn about the Monster Carnival.#l\r\n#L2# Trade #t4001129#.#l";
if (YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
talk += "\r\n#L4# ... Can I just refine my ores?#l";
}
cm.sendSimple(talk);
} else if (status == 1) {
if (selection == 0) {
@@ -269,24 +254,6 @@ function action(mode, type, selection) {
cm.warp(980030000, 0);
cm.dispose();
} else if (selection == 4) {
var selStr = "Very well, instead I offer a steadfast #bore refining#k service for you, taxing #r" + ((feeMultiplier * 100) | 0) + "%#k over the usual fee to synthetize them. What will you do?#b";
var options = ["Refine mineral ores", "Refine jewel ores"];
if (refineCrystals) {
options.push("Refine crystal ores");
}
if (refineRocks) {
options.push("Refine plates/jewels");
}
for (var i = 0; i < options.length; i++) {
selStr += "\r\n#L" + i + "# " + options[i] + "#l";
}
cm.sendSimple(selStr);
status = 76;
}
} else if (status == 2) {
select = selection;
@@ -445,170 +412,7 @@ function action(mode, type, selection) {
} else if (status == 66) {
cm.sendNext("Lastly, while in the Monster Carnival, #byou can not use items / recovery potions that you carry around with you. #kMeanwhile, the monsters let these items fall for good. when, and when you #bget them, the item will immediately activate#k. That's why it's important to know when to get these items.");
cm.dispose();
} else if (status == 77) {
var allDone;
if (selection == 0) {
allDone = refineItems(0); // minerals
} else if (selection == 1) {
allDone = refineItems(1); // jewels
} else if (selection == 2 && refineCrystals) {
allDone = refineItems(2); // crystals
} else if (selection == 2 && !refineCrystals || selection == 3) {
allDone = refineRockItems(); // moon/star rock
}
if (allDone) {
cm.sendOk("Done. Thanks for showing up~.");
} else {
cm.sendOk("Done. Be aware some of the items #rcould not be synthetized#k because either you have a lack of space on your ETC inventory or there's not enough mesos to cover the fee.");
}
cm.dispose();
}
}
}
}
function getRefineFee(fee) {
return ((feeMultiplier * fee) | 0);
}
function isRefineTarget(refineType, refineItemid) {
if (refineType == 0) { //mineral refine
return refineItemid >= 4010000 && refineItemid <= 4010007 && !(refineItemid == 4010007 && !refineSpecials);
} else if (refineType == 1) { //jewel refine
return refineItemid >= 4020000 && refineItemid <= 4020008 && !(refineItemid == 4020008 && !refineSpecials);
} else if (refineType == 2) { //crystal refine
return refineItemid >= 4004000 && refineItemid <= 4004004 && !(refineItemid == 4004004 && !refineSpecials);
}
return false;
}
function getRockRefineTarget(refineItemid) {
if (refineItemid >= 4011000 && refineItemid <= 4011006) {
return 0;
} else if (refineItemid >= 4021000 && refineItemid <= 4021008) {
return 1;
}
return -1;
}
function refineItems(refineType) {
var allDone = true;
var refineFees = [[300, 300, 300, 500, 500, 500, 800, 270], [500, 500, 500, 500, 500, 500, 500, 1000, 3000], [5000, 5000, 5000, 5000, 1000000]];
var itemCount = {};
const InventoryType = Java.type('client.inventory.InventoryType');
var iter = cm.getPlayer().getInventory(InventoryType.ETC).iterator();
while (iter.hasNext()) {
var it = iter.next();
var itemid = it.getItemId();
if (isRefineTarget(refineType, itemid)) {
var ic = itemCount[itemid];
if (ic != undefined) {
itemCount[itemid] += it.getQuantity();
} else {
itemCount[itemid] = it.getQuantity();
}
}
}
for (var key in itemCount) {
var itemqty = itemCount[key];
var itemid = parseInt(key);
var refineQty = ((itemqty / 10) | 0);
if (refineQty <= 0) {
continue;
}
while (true) {
itemqty = refineQty * 10;
var fee = getRefineFee(refineFees[refineType][(itemid % 100) | 0] * refineQty);
if (cm.canHold(itemid + 1000, refineQty, itemid, itemqty) && cm.getMeso() >= fee) {
cm.gainMeso(-fee);
cm.gainItem(itemid, -itemqty);
cm.gainItem(itemid + (itemid != 4010007 ? 1000 : 1001), refineQty);
break;
} else if (refineQty <= 1) {
allDone = false;
break;
} else {
refineQty--;
}
}
}
return allDone;
}
function refineRockItems() {
var allDone = true;
var minItems = [[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]];
var minRocks = [2147483647, 2147483647];
var rockItems = [4011007, 4021009];
var rockFees = [10000, 15000];
const InventoryType = Java.type('client.inventory.InventoryType');
var iter = cm.getPlayer().getInventory(InventoryType.ETC).iterator();
while (iter.hasNext()) {
var it = iter.next();
var itemid = it.getItemId();
var rockRefine = getRockRefineTarget(itemid);
if (rockRefine >= 0) {
var rockItem = ((itemid % 100) | 0);
var itemqty = it.getQuantity();
minItems[rockRefine][rockItem] += itemqty;
}
}
for (var i = 0; i < minRocks.length; i++) {
for (var j = 0; j < minItems[i].length; j++) {
if (minRocks[i] > minItems[i][j]) {
minRocks[i] = minItems[i][j];
}
}
if (minRocks[i] <= 0 || minRocks[i] == 2147483647) {
continue;
}
var refineQty = minRocks[i];
while (true) {
var fee = getRefineFee(rockFees[i] * refineQty);
if (cm.canHold(rockItems[i], refineQty) && cm.getMeso() >= fee) {
cm.gainMeso(-fee);
var j;
if (i == 0) {
for (j = 4011000; j < 4011007; j++) {
cm.gainItem(j, -refineQty);
}
cm.gainItem(j, refineQty);
} else {
for (j = 4021000; j < 4021009; j++) {
cm.gainItem(j, -refineQty);
}
cm.gainItem(j, refineQty);
}
break;
} else if (refineQty <= 1) {
allDone = false;
break;
} else {
refineQty--;
}
}
}
return allDone;
}
}

View File

@@ -3,6 +3,7 @@
1.0 - First Version by Drago (MapleStorySA)
2.0 - Second Version by Ronan (HeavenMS)
3.0 - Third Version by Jayd - translated CPQ contents to English and added Pirate items
Special thanks to 頼晏 (ryantpayton) for also stepping in to translate CPQ scripts.
---------------------------------------------------------------------------------------------------
**/
@@ -20,25 +21,13 @@ var cpqMaxLvl = 50;
var cpqMinAmt = 2;
var cpqMaxAmt = 6;
// Ronan's custom ore refiner NPC
var refineRocks = true; // enables moon rock, star rock
var refineCrystals = true; // enables common crystals
var refineSpecials = true; // enables lithium, special crystals
var feeMultiplier = 7.0;
function start() {
status = -1;
const YamlConfig = Java.type('config.YamlConfig');
if (!YamlConfig.config.server.USE_CPQ) {
if (YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
status = 0;
action(1, 0, 4);
} else {
cm.sendOk("The Monster Carnival is currently unavailable.");
cm.dispose();
}
cm.sendOk("The Monster Carnival is currently unavailable.");
cm.dispose();
return;
}
@@ -238,9 +227,6 @@ function action(mode, type, selection) {
} else {
if (status == 0) {
var talk = "What would you like to do? If you have never participate in the Monster Carnival, you will need to know a few things before participating! \r\n#b#L0# Go to the Monster Carnival 1.#l \r\n#L3# Go to the Monster Carnival 2.#l \r\n#L1# Learn about the Monster Carnival.#l\r\n#L2# Trade #t4001129#.#l";
if (YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
talk += "\r\n#L4# ... Can I just refine my ores?#l";
}
cm.sendSimple(talk);
} else if (status == 1) {
if (selection == 0) {
@@ -268,24 +254,6 @@ function action(mode, type, selection) {
cm.warp(980030000, 0);
cm.dispose();
} else if (selection == 4) {
var selStr = "Very well, instead I offer a steadfast #bore refining#k service for you, taxing #r" + ((feeMultiplier * 100) | 0) + "%#k over the usual fee to synthetize them. What will you do?#b";
var options = ["Refine mineral ores", "Refine jewel ores"];
if (refineCrystals) {
options.push("Refine crystal ores");
}
if (refineRocks) {
options.push("Refine plates/jewels");
}
for (var i = 0; i < options.length; i++) {
selStr += "\r\n#L" + i + "# " + options[i] + "#l";
}
cm.sendSimple(selStr);
status = 76;
}
} else if (status == 2) {
select = selection;
@@ -439,175 +407,12 @@ function action(mode, type, selection) {
cm.sendNext("Oh, and do not worry about turning into a ghost. In the Monster Carnival, #byou will not lose EXP after death#k. So it's really an experience like no other!");
cm.dispose();
} else if (select == 2) {
cm.sendNext("#bProtetor#k basically an invoked item that drastically increases the abilities of the monsters invoked by your group. Protector works until it is demolished by the opposing group, so I'm hoping you'll summon several monsters first, and then bring the Protector.");
cm.sendNext("#bProtector#k is basically an invoked item that drastically increases the abilities of the monsters invoked by your group. Protector works until it is demolished by the opposing group, so I'm hoping you'll summon several monsters first, and then bring the Protector.");
}
} else if (status == 66) {
cm.sendNext("Lastly, while in the Monster Carnival, #byou can not use items / recovery potions that you carry around with you. #kMeanwhile, the monsters let these items fall for good. when, and when you #bget them, the item will immediately activate#k. That's why it's important to know when to get these items.");
cm.dispose();
} else if (status == 77) {
var allDone;
if (selection == 0) {
allDone = refineItems(0); // minerals
} else if (selection == 1) {
allDone = refineItems(1); // jewels
} else if (selection == 2 && refineCrystals) {
allDone = refineItems(2); // crystals
} else if (selection == 2 && !refineCrystals || selection == 3) {
allDone = refineRockItems(); // moon/star rock
}
if (allDone) {
cm.sendOk("Done. Thanks for showing up~.");
} else {
cm.sendOk("Done. Be aware some of the items #rcould not be synthetized#k because either you have a lack of space on your ETC inventory or there's not enough mesos to cover the fee.");
}
cm.dispose();
}
}
}
}
function getRefineFee(fee) {
return ((feeMultiplier * fee) | 0);
}
function isRefineTarget(refineType, refineItemid) {
if (refineType == 0) { //mineral refine
return refineItemid >= 4010000 && refineItemid <= 4010007 && !(refineItemid == 4010007 && !refineSpecials);
} else if (refineType == 1) { //jewel refine
return refineItemid >= 4020000 && refineItemid <= 4020008 && !(refineItemid == 4020008 && !refineSpecials);
} else if (refineType == 2) { //crystal refine
return refineItemid >= 4004000 && refineItemid <= 4004004 && !(refineItemid == 4004004 && !refineSpecials);
}
return false;
}
function getRockRefineTarget(refineItemid) {
if (refineItemid >= 4011000 && refineItemid <= 4011006) {
return 0;
} else if (refineItemid >= 4021000 && refineItemid <= 4021008) {
return 1;
}
return -1;
}
function refineItems(refineType) {
var allDone = true;
var refineFees = [[300, 300, 300, 500, 500, 500, 800, 270], [500, 500, 500, 500, 500, 500, 500, 1000, 3000], [5000, 5000, 5000, 5000, 1000000]];
var itemCount = {};
const InventoryType = Java.type('client.inventory.InventoryType');
var iter = cm.getPlayer().getInventory(InventoryType.ETC).iterator();
while (iter.hasNext()) {
var it = iter.next();
var itemid = it.getItemId();
if (isRefineTarget(refineType, itemid)) {
var ic = itemCount[itemid];
if (ic != undefined) {
itemCount[itemid] += it.getQuantity();
} else {
itemCount[itemid] = it.getQuantity();
}
}
}
for (var key in itemCount) {
var itemqty = itemCount[key];
var itemid = parseInt(key);
var refineQty = ((itemqty / 10) | 0);
if (refineQty <= 0) {
continue;
}
while (true) {
itemqty = refineQty * 10;
var fee = getRefineFee(refineFees[refineType][(itemid % 100) | 0] * refineQty);
if (cm.canHold(itemid + 1000, refineQty, itemid, itemqty) && cm.getMeso() >= fee) {
cm.gainMeso(-fee);
cm.gainItem(itemid, -itemqty);
cm.gainItem(itemid + (itemid != 4010007 ? 1000 : 1001), refineQty);
break;
} else if (refineQty <= 1) {
allDone = false;
break;
} else {
refineQty--;
}
}
}
return allDone;
}
function refineRockItems() {
var allDone = true;
var minItems = [[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]];
var minRocks = [2147483647, 2147483647];
var rockItems = [4011007, 4021009];
var rockFees = [10000, 15000];
const InventoryType = Java.type('client.inventory.InventoryType');
var iter = cm.getPlayer().getInventory(InventoryType.ETC).iterator();
while (iter.hasNext()) {
var it = iter.next();
var itemid = it.getItemId();
var rockRefine = getRockRefineTarget(itemid);
if (rockRefine >= 0) {
var rockItem = ((itemid % 100) | 0);
var itemqty = it.getQuantity();
minItems[rockRefine][rockItem] += itemqty;
}
}
for (var i = 0; i < minRocks.length; i++) {
for (var j = 0; j < minItems[i].length; j++) {
if (minRocks[i] > minItems[i][j]) {
minRocks[i] = minItems[i][j];
}
}
if (minRocks[i] <= 0 || minRocks[i] == 2147483647) {
continue;
}
var refineQty = minRocks[i];
while (true) {
var fee = getRefineFee(rockFees[i] * refineQty);
if (cm.canHold(rockItems[i], refineQty) && cm.getMeso() >= fee) {
cm.gainMeso(-fee);
var j;
if (i == 0) {
for (j = 4011000; j < 4011007; j++) {
cm.gainItem(j, -refineQty);
}
cm.gainItem(j, refineQty);
} else {
for (j = 4021000; j < 4021009; j++) {
cm.gainItem(j, -refineQty);
}
cm.gainItem(j, refineQty);
}
break;
} else if (refineQty <= 1) {
allDone = false;
break;
} else {
refineQty--;
}
}
}
return allDone;
}
}

View File

@@ -3,6 +3,7 @@
1.0 - First Version by Drago (MapleStorySA)
2.0 - Second Version by Ronan (HeavenMS)
3.0 - Third Version by Jayd - translated CPQ contents to English and added Pirate items
Special thanks to 頼晏 (ryantpayton) for also stepping in to translate CPQ scripts.
---------------------------------------------------------------------------------------------------
**/
@@ -20,25 +21,13 @@ var cpqMaxLvl = 50;
var cpqMinAmt = 2;
var cpqMaxAmt = 6;
// Ronan's custom ore refiner NPC
var refineRocks = true; // enables moon rock, star rock
var refineCrystals = true; // enables common crystals
var refineSpecials = true; // enables lithium, special crystals
var feeMultiplier = 7.0;
function start() {
status = -1;
const YamlConfig = Java.type('config.YamlConfig');
if (!YamlConfig.config.server.USE_CPQ) {
if (YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
status = 0;
action(1, 0, 4);
} else {
cm.sendOk("The Monster Carnival is currently unavailable.");
cm.dispose();
}
cm.sendOk("The Monster Carnival is currently unavailable.");
cm.dispose();
return;
}
@@ -59,6 +48,7 @@ function action(mode, type, selection) {
status--;
}
const YamlConfig = Java.type('config.YamlConfig');
if (cm.getPlayer().getMapId() == 980000010) {
if (status == 0) {
cm.sendNext("I hope you had fun at the Monster Carnival!");
@@ -222,9 +212,9 @@ function action(mode, type, selection) {
}
} else {
var party = cm.getParty().getMembers();
if ((selection >= 0 && selection <= 3) && party.size() < 1) {
if ((selection >= 0 && selection <= 3) && party.size() < (YamlConfig.config.server.USE_ENABLE_SOLO_EXPEDITIONS ? 1 : 2)) {
cm.sendOk("You need at least 2 players to participate in the battle!");
} else if ((selection >= 4 && selection <= 5) && party.size() < 1) {
} else if ((selection >= 4 && selection <= 5) && party.size() < (YamlConfig.config.server.USE_ENABLE_SOLO_EXPEDITIONS ? 1 : 3)) {
cm.sendOk("You need at least 3 players to participate in the battle!");
} else {
cm.cpqLobby(selection);
@@ -237,11 +227,6 @@ function action(mode, type, selection) {
} else {
if (status == 0) {
var talk = "What would you like to do? If you have never participate in the Monster Carnival, you will need to know a few things before participating! \r\n#b#L0# Go to the Monster Carnival 1.#l \r\n#L3# Go to the Monster Carnival 2.#l \r\n#L1# Learn about the Monster Carnival.#l\r\n#L2# Trade #t4001129#.#l";
const YamlConfig = Java.type('config.YamlConfig');
if (YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
talk += "\r\n#L4# ... Can I just refine my ores?#l";
}
cm.sendSimple(talk);
} else if (status == 1) {
if (selection == 0) {
@@ -269,24 +254,6 @@ function action(mode, type, selection) {
cm.warp(980030000, 0);
cm.dispose();
} else if (selection == 4) {
var selStr = "Very well, instead I offer a steadfast #bore refining#k service for you, taxing #r" + ((feeMultiplier * 100) | 0) + "%#k over the usual fee to synthetize them. What will you do?#b";
var options = ["Refine mineral ores", "Refine jewel ores"];
if (refineCrystals) {
options.push("Refine crystal ores");
}
if (refineRocks) {
options.push("Refine plates/jewels");
}
for (var i = 0; i < options.length; i++) {
selStr += "\r\n#L" + i + "# " + options[i] + "#l";
}
cm.sendSimple(selStr);
status = 76;
}
} else if (status == 2) {
select = selection;
@@ -310,7 +277,7 @@ function action(mode, type, selection) {
}
} else if (select == 2) {//S2 Warrior 26 S3 Magician 6 S4 Bowman 6 S5 Thief 8
status = 10;
cm.sendSimple("Please make sure you have #t4001129# for the weapon you want. Select the weapon you would like to trade #t4001129#. The choices I have are really good, and I'm not the one who speaks to the people who say it! \r\n#b#L0# #z1302004# (" + n3 + " coins)#l\r\n#L1# #z1402006# (" + n3 + " coins)#l\r\n#L2# #z1302009# (" + n4 + " coins)#l\r\n#L3# #z1402007# (" + n4 + " coins)#l\r\n#L4# #z1302010# (" + n5 + " coins)#l\r\n#L5# #z1402003# (" + n5 + " coins)#l\r\n#L6# #z1312006# (" + n3 + " coins)#l\r\n#L7# #z1412004# (" + n3 + " coins)#l\r\n#L8# #z1312007# (" + n4 + " coins)#l\r\n#L9# #z1412005# (" + n4 + " coins)#l\r\n#L10# #z1312008# (" + n5 + " coins)#l\r\n#L11# #z1412003# (" + n5 + " coins)#l\r\n#L12# Continue to the next page(1/2)#l");
cm.sendSimple("Please make sure you have # t4001129 # for the weapon you want. Select the weapon you would like to trade # t4001129 #. The choices I have are really good, and I'm not the one who speaks to the people who say it! \r\n#b#L0# #z1302004# (" + n3 + " coins)#l\r\n#L1# #z1402006# (" + n3 + " coins)#l\r\n#L2# #z1302009# (" + n4 + " coins)#l\r\n#L3# #z1402007# (" + n4 + " coins)#l\r\n#L4# #z1302010# (" + n5 + " coins)#l\r\n#L5# #z1402003# (" + n5 + " coins)#l\r\n#L6# #z1312006# (" + n3 + " coins)#l\r\n#L7# #z1412004# (" + n3 + " coins)#l\r\n#L8# #z1312007# (" + n4 + " coins)#l\r\n#L9# #z1412005# (" + n4 + " coins)#l\r\n#L10# #z1312008# (" + n5 + " coins)#l\r\n#L11# #z1412003# (" + n5 + " coins)#l\r\n#L12# Continue to the next page (1/2)#l");
} else if (select == 3) {
status = 20;
cm.sendSimple("Select the weapon you would like to trade. The weapons I have here are extremely attractive. See for yourself! \r\n#b#L0# #z1372001# (" + n3 + " coins)#l\r\n#L1# #z1382018# (" + n3 + " coins)#l\r\n#L2# #z1372012# (" + n4 + " coins)#l\r\n#L3# #z1382019# (" + n4 + " coins)#l\r\n#L4# #z1382001# (" + n5 + " coins)#l\r\n#L5# #z1372007# (" + n5 + " coins)#l");
@@ -440,175 +407,12 @@ function action(mode, type, selection) {
cm.sendNext("Oh, and do not worry about turning into a ghost. In the Monster Carnival, #byou will not lose EXP after death#k. So it's really an experience like no other!");
cm.dispose();
} else if (select == 2) {
cm.sendNext("#bProtetor#k basically an invoked item that drastically increases the abilities of the monsters invoked by your group. Protector works until it is demolished by the opposing group, so I'm hoping you'll summon several monsters first, and then bring the Protector.");
cm.sendNext("#bProtector#k is basically an invoked item that drastically increases the abilities of the monsters invoked by your group. Protector works until it is demolished by the opposing group, so I'm hoping you'll summon several monsters first, and then bring the Protector.");
}
} else if (status == 66) {
cm.sendNext("Lastly, while in the Monster Carnival, #byou can not use items / recovery potions that you carry around with you. #kMeanwhile, the monsters let these items fall for good. when, and when you #bget them, the item will immediately activate#k. That's why it's important to know when to get these items.");
cm.dispose();
} else if (status == 77) {
var allDone;
if (selection == 0) {
allDone = refineItems(0); // minerals
} else if (selection == 1) {
allDone = refineItems(1); // jewels
} else if (selection == 2 && refineCrystals) {
allDone = refineItems(2); // crystals
} else if (selection == 2 && !refineCrystals || selection == 3) {
allDone = refineRockItems(); // moon/star rock
}
if (allDone) {
cm.sendOk("Done. Thanks for showing up~.");
} else {
cm.sendOk("Done. Be aware some of the items #rcould not be synthetized#k because either you have a lack of space on your ETC inventory or there's not enough mesos to cover the fee.");
}
cm.dispose();
}
}
}
}
function getRefineFee(fee) {
return ((feeMultiplier * fee) | 0);
}
function isRefineTarget(refineType, refineItemid) {
if (refineType == 0) { //mineral refine
return refineItemid >= 4010000 && refineItemid <= 4010007 && !(refineItemid == 4010007 && !refineSpecials);
} else if (refineType == 1) { //jewel refine
return refineItemid >= 4020000 && refineItemid <= 4020008 && !(refineItemid == 4020008 && !refineSpecials);
} else if (refineType == 2) { //crystal refine
return refineItemid >= 4004000 && refineItemid <= 4004004 && !(refineItemid == 4004004 && !refineSpecials);
}
return false;
}
function getRockRefineTarget(refineItemid) {
if (refineItemid >= 4011000 && refineItemid <= 4011006) {
return 0;
} else if (refineItemid >= 4021000 && refineItemid <= 4021008) {
return 1;
}
return -1;
}
function refineItems(refineType) {
var allDone = true;
var refineFees = [[300, 300, 300, 500, 500, 500, 800, 270], [500, 500, 500, 500, 500, 500, 500, 1000, 3000], [5000, 5000, 5000, 5000, 1000000]];
var itemCount = {};
const InventoryType = Java.type('client.inventory.InventoryType');
var iter = cm.getPlayer().getInventory(InventoryType.ETC).iterator();
while (iter.hasNext()) {
var it = iter.next();
var itemid = it.getItemId();
if (isRefineTarget(refineType, itemid)) {
var ic = itemCount[itemid];
if (ic != undefined) {
itemCount[itemid] += it.getQuantity();
} else {
itemCount[itemid] = it.getQuantity();
}
}
}
for (var key in itemCount) {
var itemqty = itemCount[key];
var itemid = parseInt(key);
var refineQty = ((itemqty / 10) | 0);
if (refineQty <= 0) {
continue;
}
while (true) {
itemqty = refineQty * 10;
var fee = getRefineFee(refineFees[refineType][(itemid % 100) | 0] * refineQty);
if (cm.canHold(itemid + 1000, refineQty, itemid, itemqty) && cm.getMeso() >= fee) {
cm.gainMeso(-fee);
cm.gainItem(itemid, -itemqty);
cm.gainItem(itemid + (itemid != 4010007 ? 1000 : 1001), refineQty);
break;
} else if (refineQty <= 1) {
allDone = false;
break;
} else {
refineQty--;
}
}
}
return allDone;
}
function refineRockItems() {
var allDone = true;
var minItems = [[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]];
var minRocks = [2147483647, 2147483647];
var rockItems = [4011007, 4021009];
var rockFees = [10000, 15000];
const InventoryType = Java.type('client.inventory.InventoryType');
var iter = cm.getPlayer().getInventory(InventoryType.ETC).iterator();
while (iter.hasNext()) {
var it = iter.next();
var itemid = it.getItemId();
var rockRefine = getRockRefineTarget(itemid);
if (rockRefine >= 0) {
var rockItem = ((itemid % 100) | 0);
var itemqty = it.getQuantity();
minItems[rockRefine][rockItem] += itemqty;
}
}
for (var i = 0; i < minRocks.length; i++) {
for (var j = 0; j < minItems[i].length; j++) {
if (minRocks[i] > minItems[i][j]) {
minRocks[i] = minItems[i][j];
}
}
if (minRocks[i] <= 0 || minRocks[i] == 2147483647) {
continue;
}
var refineQty = minRocks[i];
while (true) {
var fee = getRefineFee(rockFees[i] * refineQty);
if (cm.canHold(rockItems[i], refineQty) && cm.getMeso() >= fee) {
cm.gainMeso(-fee);
var j;
if (i == 0) {
for (j = 4011000; j < 4011007; j++) {
cm.gainItem(j, -refineQty);
}
cm.gainItem(j, refineQty);
} else {
for (j = 4021000; j < 4021009; j++) {
cm.gainItem(j, -refineQty);
}
cm.gainItem(j, refineQty);
}
break;
} else if (refineQty <= 1) {
allDone = false;
break;
} else {
refineQty--;
}
}
}
return allDone;
}
}

View File

@@ -40,10 +40,7 @@ function action(mode, type, selection) {
}
if (status == 0) {
const YamlConfig = Java.type('config.YamlConfig');
if (YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
cm.openShopNPC(2082014);
} else if (cm.isQuestStarted(3749)) {
if (cm.isQuestStarted(3749)) {
cm.sendOk("We've already located the enemy's ultimate weapon! Follow along the ship's bow area ahead and you will find my sister #b#p2082013##k. Report to her for futher instructions on the mission.");
} else {
cm.sendDefault();

View File

@@ -23,7 +23,12 @@ function action(mode, type, selection) {
}
if (!cm.isEventLeader()) {
cm.sendYesNo("I wish for your leader to talk to me. Alternatively, you may be wanting to quit. Are you going to abandon this campaign?");
// Player chose "No" or "End Chat"
if (mode <= 0) {
cm.dispose();
} else {
cm.sendYesNo("I wish for your leader to talk to me. Alternatively, you may be wanting to quit. Are you going to abandon this campaign?");
}
} else {
var eim = cm.getEventInstance();
if (eim == null) {

View File

@@ -1,11 +0,0 @@
var status = 0;
function start() {
status = -1;
action(1, 0, 0);
}
function action(mode, type, selection) {
cm.openShopNPC(2100002);
cm.dispose();
}

View File

@@ -1,11 +0,0 @@
var status = 0;
function start() {
status = -1;
action(1, 0, 0);
}
function action(mode, type, selection) {
cm.openShopNPC(2100003);
cm.dispose();
}

View File

@@ -58,8 +58,8 @@ function action(mode, type, selection) {
}
}
if (cm.getChar().getGender() == 1) {
for (var i = 0; i < fface.length; i++) {
pushIfItemExists(facenew, fface[i] + cm.getChar().getFace()
for (var i = 0; i < fface_v.length; i++) {
pushIfItemExists(facenew, fface_v[i] + cm.getChar().getFace()
% 1000 - (cm.getChar().getFace()
% 100));
}

View File

@@ -32,14 +32,14 @@ function action(mode, type, selection) {
} else if (status == 1) {
if (selection == 0) {
apqpoints = cm.getPlayer().getAriantPoints();
if (apqpoints < 100) {
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points. You need to surpass #b100 points#k so that I can give you the #bPalm Tree Beach Chair#k. Talk to me again when you have enough points.");
cm.dispose();
if (apqpoints >= 100) {
cm.sendNext("Wow, it looks like you got the #b100#k points ready to trade, let's trade?!");
} else if (apqpoints + arena.getAriantRewardTier(cm.getPlayer()) >= 100) {
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points and you pratically already have that score! Talk to my wife, #p2101016#to get them and then re-chat with me!");
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points and you pratically already have that score! Talk to my wife, #p2101016# to get them and then re-chat with me!");
cm.dispose();
} else {
cm.sendNext("Wow, it looks like you got the #b100#k points ready to trade, let's trade?!");
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points. You need to surpass #b100 points#k so that I can give you the #bPalm Tree Beach Chair#k. Talk to me again when you have enough points.");
cm.dispose();
}
} else if (selection == 1) {
cm.sendOk("The main objective of the Battle Arena is to allow the player to accumulate points so that they can be traded honorably for the highest prize: the #bPalm Tree Beach Chair#k. Collect points during the battles and talk to me when it's time to get the prize. In each battle, the player is given the opportunity to score points based on the amount of jewelry that the player has at the end. But be careful! If your points distance from other players #ris too high#k, this will have been all for nothing and you will earn mere #r1 point#k only.");

View File

@@ -17,127 +17,9 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Coco
Refining NPC:
* Chaos scroll SYNTHETIZER (rofl)
*
* @author RonanLana (ronancpl)
*/
var status = 0;
var selectedType = -1;
var selectedItem = -1;
var item;
var mats;
var matQty;
var cost;
var qty;
var equip;
var last_use; //last item is a use item
/* Coco */
function start() {
cm.getPlayer().setCS(true);
status = -1;
action(1, 0, 0);
}
function action(mode, type, selection) {
if (mode == 1) {
status++;
} else {
cm.sendOk("Oh, ok... Talk back to us when you want to make business.");
cm.dispose();
return;
}
if (status == 0) {
const YamlConfig = Java.type('config.YamlConfig');
if (!YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
cm.sendOk("Hi, I'm #b#p" + cm.getNpc() + "##k.");
cm.dispose();
return;
}
var selStr = "Hey traveler! Come, come closer... We offer a #bhuge opportunity of business#k to you. If you want to know what it is, keep listening...";
cm.sendNext(selStr);
} else if (status == 1) {
var selStr = "We've got here the knowledge to synthetize the mighty #b#t2049100##k! Of course, making one is not an easy task... But worry not! Just gather some material to me and a fee of #b1,200,000 mesos#k for our services to #bobtain it#k. You still want to do it?";
cm.sendYesNo(selStr);
} else if (status == 2) {
//selectedItem = selection;
selectedItem = 0;
var itemSet = [2049100, 7777777];
var matSet = new Array([4031203, 4001356, 4000136, 4000082, 4001126, 4080100, 4000021, 4003005]);
var matQtySet = new Array([100, 60, 40, 80, 10, 8, 200, 120]);
var costSet = [1200000, 7777777];
item = itemSet[selectedItem];
mats = matSet[selectedItem];
matQty = matQtySet[selectedItem];
cost = costSet[selectedItem];
var prompt = "So, you want us to make some #t" + item + "#? In that case, how many do you want us to make?";
cm.sendGetNumber(prompt, 1, 1, 100)
} else if (status == 3) {
qty = (selection > 0) ? selection : (selection < 0 ? -selection : 1);
last_use = false;
var prompt = "You want us to make ";
if (qty == 1) {
prompt += "a #t" + item + "#?";
} else {
prompt += qty + " #t" + item + "#?";
}
prompt += " In that case, we're going to need specific items from you in order to make it. Make sure you have room in your inventory, though!#b";
if (mats instanceof Array) {
for (var i = 0; i < mats.length; i++) {
prompt += "\r\n#i" + mats[i] + "# " + matQty[i] * qty + " #t" + mats[i] + "#";
}
} else {
prompt += "\r\n#i" + mats + "# " + matQty * qty + " #t" + mats + "#";
}
if (cost > 0) {
prompt += "\r\n#i4031138# " + cost * qty + " meso";
}
cm.sendYesNo(prompt);
} else if (status == 4) {
var complete = true;
if (cm.getMeso() < cost * qty) {
cm.sendOk("Come on! We're not here doing you a favor! We all need money to live properly, so bring the cash so we make deal and start the synthesis.");
} else if (!cm.canHold(item, qty)) {
cm.sendOk("You didn't check if you got a slot to spare on your inventory before our business, no?");
} else {
if (mats instanceof Array) {
for (var i = 0; complete && i < mats.length; i++) {
if (matQty[i] * qty == 1) {
complete = cm.haveItem(mats[i]);
} else {
complete = cm.haveItem(mats[i], matQty[i] * qty);
}
}
} else {
complete = cm.haveItem(mats, matQty * qty);
}
if (!complete) {
cm.sendOk("You kidding, right? We won't be able to start the process without all the ingredients at hands. Go get all of them and then talk to us!");
} else {
if (mats instanceof Array) {
for (var i = 0; i < mats.length; i++) {
cm.gainItem(mats[i], -matQty[i] * qty);
}
} else {
cm.gainItem(mats, -matQty * qty);
}
cm.gainMeso(-cost * qty);
cm.gainItem(item, qty);
cm.sendOk("Wow... can't believe it worked! To think for a moment that it could f... Ahem. Of course it worked, all work of ours are very efficient! Nice doing business with you.");
}
}
cm.dispose();
}
cm.sendDefault();
cm.dispose();
}

View File

@@ -19,208 +19,9 @@
*/
/* NPC: Agent E (9000036)
Victoria Road : Henesys
Refining NPC:
* Accessories refiner
*
* @author Ronan Lana
*/
var status = -1;
var selectedType = -1;
var selectedItem = -1;
var item;
var items;
var mats;
var matQty;
var cost;
var qty = 1;
var equip;
var maxEqp = 0;
function start() {
const YamlConfig = Java.type('config.YamlConfig');
if (!YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
cm.sendOk("Hi, I'm #b#p" + cm.getNpc() + "##k.");
cm.dispose();
return;
}
cm.getPlayer().setCS(true);
var selStr = "Hello, I am the #bAccessory NPC Crafter#k! My works are widely recognized to be too fine, up to the point at which all my items mimic not only the appearance but too the attributes of them! Everything I charge is some 'ingredients' to make them and, of course, a fee for my services. On what kind of equipment are you interessed?#b";
var options = ["Pendants", "Face accessories", "Eye accessories", "Belts & medals", "Rings"/*,"#t4032496#"*/];
for (var i = 0; i < options.length; i++) {
selStr += "\r\n#L" + i + "# " + options[i] + "#l";
}
cm.sendSimple(selStr);
}
function action(mode, type, selection) {
status++;
if (mode != 1) {
cm.dispose();
return;
}
if (status == 0) {
if (selection == 0) { //pendants
var selStr = "Well, I've got these pendants on my repertoire:#b";
items = [1122018, 1122007, 1122001, 1122003, 1122004, 1122006, 1122002, 1122005, 1122058];
for (var i = 0; i < items.length; i++) {
selStr += "\r\n#L" + i + "##t" + items[i] + "##b";
}
} else if (selection == 1) { //face accessory
var selStr = "Hmm, face accessories? There you go: #b";
items = [1012181, 1012182, 1012183, 1012184, 1012185, 1012186, 1012108, 1012109, 1012110, 1012111];
for (var i = 0; i < items.length; i++) {
selStr += "\r\n#L" + i + "##t" + items[i] + "##b";
}
} else if (selection == 2) { //eye accessory
var selStr = "Got hard sight? Okay, so which glasses do you want me to make?#b";
items = [1022073, 1022088, 1022103, 1022089, 1022082];
for (var i = 0; i < items.length; i++) {
selStr += "\r\n#L" + i + "##t" + items[i] + "##b";
}
} else if (selection == 3) { //belt & medal
var selStr = "Hmm... For these, things get a little tricky. Since these items are too short and too similar one another, I don't really know what item will emerge when I finish the synthesis. Still wanna try for something?";
items = [];
maxEqp = 0;
for (var x = 1132005; x < 1132017; maxEqp++, x++) {
items[maxEqp] = x;
}
for (var x = 1142000; x < 1142102; maxEqp++, x++) {
items[maxEqp] = x;
}
for (var x = 1142107; x < 1142121; maxEqp++, x++) {
items[maxEqp] = x;
}
for (var x = 1142122; x < 1142143; maxEqp++, x++) {
items[maxEqp] = x;
}
selStr += "\r\n#L" + i + "##bTry it!#b";
} else if (selection == 4) { //ring refine
var selStr = "Rings, huh? These are my specialty, go check it yourself!#b";
items = [1112407, 1112408, 1112401, 1112413, 1112414, 1112405, 1112402];
for (var i = 0; i < items.length; i++) {
selStr += "\r\n#L" + i + "##t" + items[i] + "##b";
}
}/*else if (selection == 5) { //make necklace
var selStr = "Need to make #t4032496#?#b";
items = [4032496];
for (var i = 0; i < items.length; i++)
selStr += "\r\n#L" + i + "##t" + items[i] + "##l";
}*/
selectedType = selection;
cm.sendSimple(selStr);
} else if (status == 1) {
if (selectedType != 3) {
selectedItem = selection;
}
if (selectedType == 0) { //pendant refine
var matSet = [[4003004, 4030012, 4001356, 4000026], [4000026, 4001356, 4000073, 4001006], [4001343, 4011002, 4003004, 4003005], [4001343, 4011006, 4003004, 4003005], [4000091, 4011005, 4003004, 4003005], [4000091, 4011001, 4003004, 4003005], [4000469, 4011000, 4003004, 4003005], [4000469, 4011004, 4003004, 4003005], [1122007, 4003002, 4000413]];
var matQtySet = [[20, 20, 5, 1], [5, 5, 10, 1], [10, 2, 20, 4], [10, 1, 20, 4], [15, 3, 30, 6], [15, 3, 30, 6], [20, 5, 20, 8], [20, 4, 40, 8], [1, 1, 1]];
var costSet = [150000, 500000, 200000, 200000, 300000, 300000, 400000, 400000, 2500000];
} else if (selectedType == 1) { //face accessory refine
var matSet = [[4006000, 4003004], [4006000, 4003004, 4000026], [4006000, 4003004, 4000026, 4000082, 4003002], [4006000, 4003005], [4006000, 4003005, 4000026], [4006000, 4003005, 4000026, 4000082, 4003002], [4001006, 4011008], [4001006, 4011008], [4001006, 4011008], [4001006, 4011008]];
var matQtySet = [[5, 5], [5, 5, 5], [5, 5, 5, 5, 1], [5, 5], [5, 5, 5], [5, 5, 5, 5, 1], [1, 1], [1, 1], [1, 1], [1, 1]];
var costSet = [100000, 200000, 300000, 125000, 250000, 375000, 500000, 500000, 500000, 500000, 25000, 25000, 25000, 25000];
} else if (selectedType == 2) { //eye accessory refine
var matSet = [[4001006, 4003002, 4000082, 4031203], [4001005, 4011008], [4001005, 4011008], [4001005, 4011008, 4000082], [4001006, 4003002, 4003000, 4003001]];
var matQtySet = [[2, 2, 5, 10], [3, 2], [4, 3], [5, 3, 10], [2, 2, 10, 5]];
var costSet = [250000, 250000, 300000, 400000, 200000];
} else if (selectedType == 3) { //belt & medals refine
var matSet = [[4001006, 4003005, 4003004], [7777, 7777]];
var matQtySet = [[2, 5, 10], [7777, 7777]];
var costSet = [15000, 7777];
} else if (selectedType == 4) { //ring refine
var matSet = [[4003001, 4001344, 4006000], [4003001, 4001344, 4006000], [4021004, 4011008], [4011008, 4001006], [1112413, 2022039], [1112414, 4000176], [4011007, 4021009]];
var matQtySet = [[2, 2, 2], [2, 2, 2], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1]];
var costSet = [10000, 10000, 10000, 20000, 15000, 15000, 10000];
}/*else if (selectedType == 5) { //necklace refine
var matSet = [[4011007, 4011008, 4021009]];
var matQtySet = [[1, 1, 1]];
var costSet = [10000];
}*/
if (selectedType == 3) {
selectedItem = Math.floor(Math.random() * maxEqp);
item = items[selectedItem];
mats = matSet[0];
matQty = matQtySet[0];
cost = costSet[0];
} else {
item = items[selectedItem];
mats = matSet[selectedItem];
matQty = matQtySet[selectedItem];
cost = costSet[selectedItem];
}
var prompt = "You want me to make ";
if (selectedType != 3) {
if (qty == 1) {
prompt += "a #b#t" + item + "##k?";
} else {
prompt += "#b" + qty + " #t" + item + "##k?";
}
} else {
prompt += "a #bbelt#k or a #bmedal#k?";
}
prompt += " Right! I will need some items to make that item. Make sure you have a #bfree slot#k in your inventory!#b";
if (mats instanceof Array) {
for (var i = 0; i < mats.length; i++) {
prompt += "\r\n#i" + mats[i] + "# " + (matQty[i] * qty) + " #t" + mats[i] + "#";
}
} else {
prompt += "\r\n#i" + mats + "# " + (matQty * qty) + " #t" + mats + "#";
}
if (cost > 0) {
prompt += "\r\n#i4031138# " + (cost * qty) + " meso";
}
cm.sendYesNo(prompt);
} else if (status == 2) {
if (cm.getMeso() < (cost * qty)) {
cm.sendOk("This is the fee I charge to make my items! No credit.");
} else {
var complete = true;
if (mats instanceof Array) {
for (var i = 0; complete && i < mats.length; i++) {
if (!cm.haveItem(mats[i], matQty[i] * qty)) {
complete = false;
}
}
} else if (!cm.haveItem(mats, matQty * qty)) {
complete = false;
}
if (!complete) {
cm.sendOk("Are you sure you got all the items required? Double check it!");
} else {
if (cm.canHold(item, qty)) {
if (mats instanceof Array) {
for (var i = 0; i < mats.length; i++) {
cm.gainItem(mats[i], -(matQty[i] * qty));
}
} else {
cm.gainItem(mats, -(matQty * qty));
}
cm.gainMeso(-(cost * qty));
cm.gainItem(item, qty);
cm.sendOk("The item is done! Take and try this piece of art yourself.");
} else {
cm.sendOk("You got no free slot on your inventory.");
}
}
}
cm.dispose();
}
cm.sendDefault();
cm.dispose();
}

View File

@@ -19,72 +19,9 @@
*/
/* Dalair
Medal NPC.
NPC Equipment Merger:
* @author Ronan Lana
*/
var status;
var mergeFee = 50000;
var name;
function start() {
status = -1;
action(1, 0, 0);
}
function action(mode, type, selection) {
if (mode == -1) {
cm.dispose();
} else {
if (mode == 0 && type > 0) {
cm.dispose();
return;
}
if (mode == 1) {
status++;
} else {
status--;
}
if (status == 0) {
const YamlConfig = Java.type('config.YamlConfig');
if (!YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
cm.sendOk("The medal ranking system is currently unavailable...");
cm.dispose();
return;
}
var levelLimit = !cm.getPlayer().isCygnus() ? 160 : 110;
var selStr = "The medal ranking system is currently unavailable... Therefore, I am providing the #bEquipment Merge#k service! ";
const MakerProcessor = Java.type('client.processor.action.MakerProcessor');
if (!YamlConfig.config.server.USE_STARTER_MERGE && (cm.getPlayer().getLevel() < levelLimit || MakerProcessor.getMakerSkillLevel(cm.getPlayer()) < 3)) {
selStr += "However, you must have #rMaker level 3#k and at least #rlevel 110#k (Cygnus Knight), #rlevel 160#k (other classes) and a fund of #r" + cm.numberWithCommas(mergeFee) + " mesos#k to use the service.";
cm.sendOk(selStr);
cm.dispose();
} else if (cm.getMeso() < mergeFee) {
selStr += "I'm sorry, but this service tax is of #r" + cm.numberWithCommas(mergeFee) + " mesos#k, which it seems you unfortunately don't have right now... Please, stop by again later.";
cm.sendOk(selStr);
cm.dispose();
} else {
selStr += "For the fee of #r" + cm.numberWithCommas(mergeFee) + "#k mesos, merge unnecessary equipments in your inventory into your currently equipped gears to get stat boosts into them, statups based on the attributes of the items used on the merge!";
cm.sendNext(selStr);
}
} else if (status == 1) {
selStr = "#rWARNING#b: Make sure you have your items ready to merge at the slots #rAFTER#b the item you have selected to merge.#k Any items #bunder#k the item selected will be merged thoroughly.\r\n\r\nNote that equipments receiving bonuses from merge are going to become #rUntradeable#k thereon, and equipments that already received the merge bonus #rcannot be used for merge#k.\r\n\r\n";
cm.sendGetText(selStr);
} else if (status == 2) {
name = cm.getText();
if (cm.getPlayer().mergeAllItemsFromName(name)) {
cm.gainMeso(-mergeFee);
cm.sendOk("Merging complete! Thanks for using the service and enjoy your new equipment stats.");
} else {
cm.sendOk("There is no #b'" + name + "'#k in your #bEQUIP#k inventory!");
}
cm.dispose();
}
}
cm.sendOk("The medal ranking system is currently unavailable...");
cm.dispose();
}

View File

@@ -19,54 +19,9 @@
*/
/* NPC: Donation Box (9000041)
Victoria Road : Henesys
NPC Bazaar:
* @author Ronan Lana
*/
var options = ["EQUIP", "USE", "SET-UP", "ETC"];
var name;
var status;
var selectedType = 0;
function start() {
status = -1;
action(1, 0, 0);
cm.sendOk("The medal ranking system is currently unavailable...");
cm.dispose();
}
function action(mode, type, selection) {
status++;
if (mode != 1) {
cm.dispose();
return;
}
if (status == 0) {
const YamlConfig = Java.type('config.YamlConfig');
if (!YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
cm.sendOk("The medal ranking system is currently unavailable...");
cm.dispose();
return;
}
var selStr = "Hello, I am the #bBazaar NPC#k! Sell to me any item on your inventory you don't need. #rWARNING#b: Make sure you have your items ready to sell at the slots #rAFTER#b the item you have selected to sell.#k Any items #bunder#k the item selected will be sold thoroughly.";
for (var i = 0; i < options.length; i++) {
selStr += "\r\n#L" + i + "# " + options[i] + "#l";
}
cm.sendSimple(selStr);
} else if (status == 1) {
selectedType = selection;
cm.sendGetText("From what item on your #r" + options[selectedType] + "#k inventory do you want to start the transaction?");
} else if (status == 2) {
name = cm.getText();
var res = cm.getPlayer().sellAllItemsFromName(selectedType + 1, name);
if (res > -1) {
cm.sendOk("Transaction complete! You received #r" + cm.numberWithCommas(res) + " mesos#k from this action.");
} else {
cm.sendOk("There is no #b'" + name + "'#k in your #b" + options[selectedType] + "#k inventory!");
}
cm.dispose();
}
}

View File

@@ -46,7 +46,7 @@ function action(mode, type, selection) {
cm.dispose();
return;
}
if (cm.getMeso < price) {
if (cm.getMeso() < price) {
cm.sendOk("Please check and see if you have " + price + " mesos to enter this place.");
} else {
cm.gainMeso(-price);

View File

@@ -1,14 +1,88 @@
/**
*9201098 - Mo
*@author Ronan
* 9201098 - Mo
* @author Ronan
* @author Ponk
*/
let status = 0;
let selectedItem = undefined;
/*
References:
- https://www.youtube.com/watch?v=g6y2zmCGglI
- https://www.youtube.com/watch?v=CttmlVWLJKM
*/
function start() {
if (cm.getQuestStatus(8224) == 2) {
cm.openShopNPC(9201099);
} else {
cm.sendOk("Hm, at who do you think you are looking at?");
if (cm.getQuestStatus(8224) !== 2) {
cm.sendDefault();
cm.dispose();
return;
}
cm.dispose();
cm.sendSimple("Name's Mo. I've got Mo' items for Mo' mesos. What business do you bring me?\r\n#L0##bI'd like to buy some items#k");
}
function action(action, type, selection) {
if (!action) {
cm.dispose();
return;
}
if (status === 0) {
let index = 0;
const selections = "#e" + shopItems()
.map(i => {
const mesoText = i.quantity === 1 ? "meso" : `meso per ${i.quantity} arrows`;
return `\r\n#L${index++}##i${i.itemId}# #z${i.itemId}# #b${i.cost} ${mesoText}#k`;
})
.join("");
cm.sendSimple("An ally of the Raven Ninja Clan is welcome to buy from me!" + selections);
status++;
} else if (status === 1 && selection !== -1) {
selectedItem = shopItems()[selection];
cm.sendAcceptDecline("Are you sure you want to buy it?");
status++;
} else if (status === 2) {
if (!selectedItem) {
cm.dispose();
return;
}
if (!cm.hasMeso(selectedItem.cost)) {
cm.sendOk("You don't have enough mesos.");
cm.dispose();
return;
}
if (!cm.canHold(selectedItem.itemId, selectedItem.quantity)) {
cm.sendOk("There's no room in your inventory.");
cm.dispose();
return;
}
cm.loseMeso(selectedItem.cost);
cm.gainItem(selectedItem.itemId, selectedItem.quantity);
cm.dispose();
}
}
function shopItems() {
return [
{itemId: 2050004, quantity: 1, cost: 400}, // All-Cure Potion
{itemId: 2050000, quantity: 1, cost: 200}, // Antidote
{itemId: 2020012, quantity: 1, cost: 4500}, // Melting Cheese
{itemId: 2020013, quantity: 1, cost: 5000}, // Reindeer Milk
{itemId: 2020014, quantity: 1, cost: 8100}, // Sunrise Dew
{itemId: 2020015, quantity: 1, cost: 9690}, // Sunset Dew
{itemId: 2050001, quantity: 1, cost: 200}, // Eyedrop
{itemId: 2050002, quantity: 1, cost: 300}, // Tonic
{itemId: 2050003, quantity: 1, cost: 500}, // Holy Water
{itemId: 2022000, quantity: 1, cost: 1650}, // Pure Water
{itemId: 2002017, quantity: 1, cost: 5000}, // Warrior Elixir
{itemId: 2060004, quantity: 2000, cost: 40_000}, // Diamond Arrow for Bow
{itemId: 2061004, quantity: 2000, cost: 40_000}, // Diamond Arrow for Crossbow
{itemId: 2070010, quantity: 1, cost: 2000}, // Icicle
{itemId: 2022003, quantity: 1, cost: 1100}, // Unagi
{itemId: 2000006, quantity: 1, cost: 620}, // Mana Elixir
{itemId: 2022002, quantity: 1, cost: 1000}, // Cider
{itemId: 2030020, quantity: 1, cost: 400}, // Return to New Leaf City Scroll
]
}

View File

@@ -4,13 +4,6 @@
*/
function start() {
const YamlConfig = Java.type('config.YamlConfig');
if (YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
cm.openShopNPC(9201101);
} else {
//cm.sendOk("The patrol in New Leaf City is always ready. No creatures are able to break through to the city.");
cm.sendDefault();
}
cm.sendDefault();
cm.dispose();
}

View File

@@ -24,7 +24,6 @@
var status;
var anthemSong = "Field/anthem/brazil"; // sound src: https://c7.rbxcdn.com/f91060652a6e9fbfbf92cb1418435448
var ambientSong = "Bgm04/Shinin'Harbor";
var feature_tree = [];
@@ -217,7 +216,6 @@ function writeFeatureTab_Serverpotentials() {
addFeature("Poison damage value visible for other players.");
addFeature("M. book announcer displays info based on demand.");
addFeature("Custom jail system.");
addFeature("Custom buyback system, uses mesos / NX, via MTS.");
addFeature("Custom fishing system having 'seasonal' catch times.");
addFeature("Actual fishing handling w/ F. Net - thanks Dragohe4rt!");
addFeature("Custom map leasing system.");
@@ -260,7 +258,7 @@ function writeFeatureTab_CustomNPCs() {
function writeFeatureTab_Localhostedits() {
addFeature("Removed the 'n' NPC dialog issue.");
addFeature("Removed caps for MATK, WMDEF, ACC and AVOID.");
addFeature("Removed MTS block, buyback available anywhere.");
addFeature("Removed MTS block.");
addFeature("Removed party blocks for novices under level 10.");
addFeature("Set a much more higher cap for SPEED.");
addFeature("Removed AP usage block for novices.");
@@ -308,8 +306,6 @@ function writeAllFeatures() {
}
function start() {
const PacketCreator = Java.type('tools.PacketCreator');
cm.getPlayer().sendPacket(PacketCreator.musicChange(anthemSong));
status = -1;
writeAllFeatures();
action(1, 0, 0);
@@ -369,4 +365,4 @@ function generateSelectionMenu(array) {
menu += "#L" + i + "#" + array[i] + "#l\r\n";
}
return menu;
}
}

View File

@@ -14,9 +14,9 @@ var staff_heading = "!";
var levels = ["Common", "Donator", "JrGM", "GM", "SuperGM", "Developer", "Admin"];
var commands;
// Expectation: "ce" bound to an instance of java.client.command.CommandsExecutor
function writeHeavenMSCommands() {
const CommandsExecutor = Java.type('client.command.CommandsExecutor');
commands = CommandsExecutor.getInstance().getGmCommands();
commands = ce.getGmCommands();
}
function start() {

View File

@@ -21,37 +21,6 @@
Default Maple TV
*/
var status;
function start() {
const YamlConfig = Java.type('config.YamlConfig');
if (YamlConfig.config.server.USE_ENABLE_CUSTOM_NPC_SCRIPT) {
cm.dispose();
cm.openNpc(9201088, "scroll_generator");
return;
}
status = -1;
action(1, 0, 0);
}
function action(mode, type, selection) {
if (mode == -1) {
cm.dispose();
} else {
if (mode == 0 && type > 0) {
cm.dispose();
return;
}
if (mode == 1) {
status++;
} else {
status--;
}
if (status == 0) {
// do nothing
cm.dispose();
}
}
cm.dispose();
}

View File

@@ -1,79 +0,0 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Rebirth NPC
@author Ronan
@author wejrox
*/
var status;
var jobId = 0;
function start() {
status = -1;
const YamlConfig = Java.type('config.YamlConfig');
if (!YamlConfig.config.server.USE_REBIRTH_SYSTEM) {
cm.sendOk("Rebirths aren't enabled on this server, how did you get here?");
cm.dispose();
return;
}
action(1, 0, 0);
}
function action(mode, type, selection) {
if (mode === 1) {
status++;
} else {
cm.dispose();
return;
}
if (status === 0) {
cm.sendNext("Come to me when you want to be reborn again. You currently have a total of #r" + cm.getChar().getReborns() + " #krebirths.");
} else if (status === 1) {
cm.sendSimple("What do you want me to do today: \r\n \r\n #L0##bI want to be reborn!#l \r\n #L1##bNothing for now...#k#l");
} else if (status === 2) {
if (selection === 0) {
if (cm.getChar().getLevel() === cm.getChar().getMaxClassLevel()) {
cm.sendSimple("I see... and which path would you like to take? \r\n\r\n #L0##bExplorer (Beginner)#l \r\n #L1##bCygnus Knight (Noblesse)#l \r\n #L2##bAran (Legend)#l");
} else {
cm.sendOk("It looks like your journey has not yet ended... come back when you're level " + cm.getChar().getMaxClassLevel());
cm.dispose();
}
} else if (selection === 1) {
cm.sendOk("See you soon!")
cm.dispose();
}
} else if (status === 3) {
// 0 => beginner, 1000 => noblesse, 2000 => legend
// makes this very easy :-)
jobId = selection * 1000;
var job = "";
if (selection === 0) job = "Beginner";
else if (selection === 1) job = "Noblesse";
else if (selection === 2) job = "Legend";
cm.sendYesNo("Are you sure you want to be reborn as a " + job + "?");
}
else if (status === 4 && type === 1) {
cm.getChar().executeRebornAsId(jobId);
cm.sendOk("You have now been reborn. That's a total of #r" + cm.getChar().getReborns() + "#k rebirths");
cm.dispose();
}
}

View File

@@ -1,452 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* NPC: MapleTV / Larry
Exchanger NPC:
* Scroll generator
*
* @author Ronan Lana
*/
var status;
var jobWeaponRestricted = [[[2043000, 2043100, 2044000, 2044100, 2043200, 2044200]], [[2043000, 2043100, 2044000, 2044100], [2043000, 2043200, 2044000, 2044200], [2044300, 2044400]], [[2043700, 2043800], [2043700, 2043800], [2043700, 2043800]], [[2044500], [2044600]], [[2044700], [2043300]], [[2044800], [2044900]]];
var aranWeaponRestricted = [jobWeaponRestricted[1][2][1]];
var tier1Scrolls = [];
var tier2Scrolls = [2040000, 2040400, 2040500, 2040600, 2040700, 2040800, 2040900];
var tier3Scrolls = [2048000, 2049200, 2041000, 2041100, 2041300, 2040100, 2040200, 2040300];
var typeTierScrolls = [["PAD", "MAD"], ["STR", "DEX", "INT", "LUK", "ACC", "EVA", "Speed", "Jump"], ["PDD", "MDD", "MHP", "MMP"]];
var sgItems = [4003004, 4003005, 4001006, 4006000, 4006001, 4030012];
var sgToBucket = [100, 50, 37.5, 37.5, 37.5, 200];
var mesoToBucket = 2800000;
var sgAppliedItems = [0, 0, 0, 0, 0, 0];
var sgAppliedMeso = 0;
var sgBuckets = 0.0;
var sgBookBuckets = 0.0;
var sgItemBuckets = 0.0;
function start() {
status = -1;
action(1, 0, 0);
}
function action(mode, type, selection) {
if (mode == -1) {
cm.dispose();
} else {
if (mode == 0 && type > 0) {
cm.dispose();
return;
}
if (mode == 1) {
status++;
} else {
status--;
}
if (status == 0) {
cm.sendNext("This is the MapleTV Scroll Generator broadcast. Place your supplies or mesos earned throughout your adventure to redeem a prize! You can place #bany amount of supplies#k, however take note that placing #rdifferent supplies#k with #rbigger shots of any of them#k will improve the reward possibilities!");
} else if (status == 1) {
var sendStr;
//print("Book: " + sgBookBuckets + " Item: " + sgItemBuckets);
if (sgItemBuckets > 0.0) {
sendStr = "With the items you have currently placed, you have #r" + sgBuckets + "#k buckets (#r" + (sgItemBuckets < 1.0 ? sgItemBuckets.toFixed(2) : Math.floor(sgItemBuckets)) + "#k supply buckets) for claiming a prize. Place supplies:";
} else {
sendStr = "You have placed no supplies yet. Place supplies:";
}
var listStr = "";
var i;
for (i = 0; i < sgItems.length; i++) {
listStr += "#b#L" + i + "##t" + sgItems[i] + "##k";
if (sgAppliedItems[i] > 0) {
listStr += " - " + sgAppliedItems[i];
}
listStr += "#l\r\n";
}
listStr += "#b#L" + i + "#Mesos#k";
if (sgAppliedMeso > 0) {
listStr += " - " + sgAppliedMeso;
}
listStr += "#l\r\n";
cm.sendSimple(sendStr + "\r\n\r\n" + listStr + "#r#L" + (sgItems.length + 2) + "#Retrieve a prize!#l#k\r\n");
} else if (status == 2) {
if (selection == (sgItems.length + 2)) {
if (sgItemBuckets < 1.0) {
cm.sendPrev("You have set not enough supplies. Insert at least one bucket of #bsupplies#k to claim a prize.");
} else {
generateRandomScroll();
cm.dispose();
}
} else {
var tickSel;
if (selection < sgItems.length) {
tickSel = "of #b#t" + sgItems[selection] + "##k";
curItemQty = cm.getItemQuantity(sgItems[selection]);
} else {
tickSel = "#bmesos#k";
curItemQty = cm.getMeso();
}
curItemSel = selection;
if (curItemQty > 0) {
cm.sendGetText("How many " + tickSel + " do you want to provide? (#r" + curItemQty + "#k available)#k");
} else {
cm.sendPrev("You have got #rnone#k " + tickSel + " to provide for Scroll Generation. Click '#rBack#k' to return to the main interface.");
}
}
} else if (status == 3) {
var text = cm.getText();
try {
var placedQty = parseInt(text);
if (isNaN(placedQty) || placedQty < 0) {
throw true;
}
if (placedQty > curItemQty) {
cm.sendPrev("You cannot insert the given amount of #r" + (curItemSel < sgItems.length ? "#t" + sgItems[curItemSel] + "#" : "mesos") + "#k (#r" + curItemQty + "#k available). Click '#rBack#k' to return to the main interface.");
} else {
if (curItemSel < sgItems.length) {
sgApplyItem(curItemSel, placedQty);
} else {
sgApplyMeso(placedQty);
}
cm.sendPrev("Operation succeeded. Click '#rBack#k' to return to the main interface.");
}
} catch (err) {
cm.sendPrev("You must enter a positive number of supplies to insert. Click '#rBack#k' to return to the main interface.");
}
status = 2;
} else {
cm.dispose();
}
}
}
function getJobTierScrolls() {
var scrolls = [];
var job = cm.getPlayer().getJob();
var jobScrolls = jobWeaponRestricted[Math.floor(cm.getPlayer().getJobStyle().getId() / 100)];
const GameConstants = Java.type('constants.game.GameConstants');
var jobBranch = GameConstants.getJobBranch(job);
if (jobBranch >= 2) {
Array.prototype.push.apply(scrolls, jobScrolls[Math.floor((job.getId() / 10) % 10) - 1]);
} else {
for (var i = 0; i < jobScrolls.length; i++) {
Array.prototype.push.apply(scrolls, jobScrolls[i]);
}
}
return scrolls;
}
function getScrollTypePool(rewardTier) {
var scrolls = [];
switch (rewardTier) {
case 1:
if (cm.getPlayer().isAran()) {
Array.prototype.push.apply(scrolls, aranWeaponRestricted);
} else {
Array.prototype.push.apply(scrolls, getJobTierScrolls());
}
Array.prototype.push.apply(scrolls, tier1Scrolls);
break;
case 2:
Array.prototype.push.apply(scrolls, tier2Scrolls);
break;
default:
Array.prototype.push.apply(scrolls, tier3Scrolls);
}
return scrolls;
}
function getScrollTier(scrollStats) {
for (var i = 0; i < typeTierScrolls.length; i++) {
for (var j = 0; j < typeTierScrolls[i].length; j++) {
if (scrollStats.get(typeTierScrolls[i][j]) > 0) {
return i + 1;
}
}
}
return 4;
}
function getScrollSuccessTier(scrollStats) {
var prop = scrollStats.get("success");
const YamlConfig = Java.type('config.YamlConfig');
if (prop > 90) {
return 3;
} else if (prop < 50) {
return YamlConfig.config.server.SCROLL_CHANCE_ROLLS > 2 ? 2 : 1;
} else {
return YamlConfig.config.server.SCROLL_CHANCE_ROLLS > 2 ? 1 : 2;
}
}
function getAvailableScrollsPool(baseScrolls, rewardTier, successTier) {
var scrolls = [];
const ItemInformationProvider = Java.type('server.ItemInformationProvider');
var ii = ItemInformationProvider.getInstance();
for (var i = 0; i < baseScrolls.length; i++) {
for (var j = 0; j < 100; j++) {
var scrollid = baseScrolls[i] + j;
var scrollStats = ii.getEquipStats(scrollid);
if (scrollStats != null && ii.getScrollReqs(scrollid).isEmpty()) {
var scrollTier = getScrollTier(scrollStats);
if (scrollTier == rewardTier && successTier == getScrollSuccessTier(scrollStats)) {
scrolls.push(scrollid);
}
}
}
}
return scrolls;
}
// passive tier buckets...
function getLevelTier(level) {
return Math.floor((level - 1) / 15) + 1;
}
function getPlayerCardTierPower() {
var cardset = cm.getPlayer().getMonsterBook().getCardSet();
var countTier = [0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var iterator = cardset.iterator(); iterator.hasNext();) {
var ce = iterator.next();
var cardid = ce.getKey();
var ceTier = Math.floor(cardid / 1000) % 10;
countTier[ceTier] += ce.getValue();
if (ceTier >= 8) { // is special card
const LifeFactory = Java.type('server.life.LifeFactory');
const ItemInformationProvider = Java.type('server.ItemInformationProvider');
var mobLevel = LifeFactory.getMonsterLevel(ItemInformationProvider.getInstance().getCardMobId(cardid));
var mobTier = getLevelTier(mobLevel) - 1;
countTier[mobTier] += (ce.getValue() * 1.2);
}
}
return countTier;
}
function calculateMobBookTierBuckets(tierSize, playerCards, tier) {
if (tier < 1) {
return 0.0;
}
tier--; // started at 1
var tierHitRate = playerCards[tier] / (tierSize[tier] * 5);
if (tierHitRate > 0.5) {
tierHitRate = 0.5;
}
return tierHitRate * 4;
}
function calculateMobBookBuckets() {
var book = cm.getPlayer().getMonsterBook();
var bookLevelMult = 0.9 + (0.1 * book.getBookLevel());
var playerLevelTier = getLevelTier(cm.getPlayer().getLevel());
if (playerLevelTier > 8) {
playerLevelTier = 8;
}
const MonsterBook = Java.type('client.MonsterBook');
var tierSize = MonsterBook.getCardTierSize();
var playerCards = getPlayerCardTierPower();
var prevBuckets = calculateMobBookTierBuckets(tierSize, playerCards, playerLevelTier - 1);
var currBuckets = calculateMobBookTierBuckets(tierSize, playerCards, playerLevelTier);
return (prevBuckets + currBuckets) * bookLevelMult;
}
function recalcBuckets() {
sgBookBuckets = calculateMobBookBuckets();
sgItemBuckets = calculateSuppliesBuckets();
var buckets = sgBookBuckets + sgItemBuckets;
if (buckets > 6.0) {
sgBuckets = 6;
} else {
sgBuckets = Math.floor(buckets);
}
}
// variable buckets...
function sgApplyItem(idx, amount) {
if (sgAppliedItems[idx] != amount) {
sgAppliedItems[idx] = amount;
recalcBuckets();
}
}
function sgApplyMeso(amount) {
if (sgAppliedMeso != amount) {
sgAppliedMeso = amount;
recalcBuckets();
}
}
function calculateSuppliesBuckets() {
var suppliesHitRate = 0.0;
for (var i = 0; i < sgItems.length; i++) {
suppliesHitRate += sgAppliedItems[i] / sgToBucket[i];
}
suppliesHitRate *= 2;
suppliesHitRate += (sgAppliedMeso / mesoToBucket);
return suppliesHitRate;
}
function calculateScrollTiers() {
var buckets = sgBuckets;
var tiers = [0, 0, 0];
while (buckets > 0) {
var pool = [];
for (var i = 0; i < tiers.length; i++) {
if (tiers[i] < 2) {
pool.push(i);
}
}
var rnd = pool[Math.floor(Math.random() * pool.length)];
tiers[rnd]++;
buckets--;
}
// normalize tiers
for (var i = 0; i < tiers.length; i++) {
tiers[i] = 3 - tiers[i];
}
return tiers;
}
function getRandomScrollFromTiers(tiers) {
var typeTier = tiers[0], subtypeTier = tiers[1], successTier = tiers[2];
var scrollTypePool = getScrollTypePool(typeTier);
var scrollPool = getAvailableScrollsPool(scrollTypePool, subtypeTier, successTier);
if (scrollPool.length > 0) {
return scrollPool[Math.floor(Math.random() * scrollPool.length)];
} else {
return -1;
}
}
function getRandomScrollFromRightPermutations(tiers) {
for (var i = 2; i >= 0; i--) {
for (var j = i - 1; j >= 0; j--) {
if (tiers[i] >= 3) {
break;
} else if (tiers[j] > 1) {
tiers[i]++;
tiers[j]--;
var itemid = getRandomScrollFromTiers(tiers);
if (itemid != -1) {
return itemid;
}
}
}
}
return -1;
}
function getRandomScroll(tiers) {
var itemid = getRandomScrollFromTiers(tiers);
if (itemid == -1) {
// worst case shift-right permutations...
itemid = getRandomScrollFromRightPermutations(tiers);
}
return itemid;
}
function performExchange(sgItemid, sgCount) {
if (cm.getMeso() < sgAppliedMeso) {
return false;
}
for (var i = 0; i < sgItems.length; i++) {
var itemid = sgItems[i];
var count = sgAppliedItems[i];
if (count > 0 && !cm.haveItem(itemid, count)) {
return false;
}
}
cm.gainMeso(-sgAppliedMeso);
for (var i = 0; i < sgItems.length; i++) {
var itemid = sgItems[i];
var count = sgAppliedItems[i];
cm.gainItem(itemid, -count);
}
cm.gainItem(sgItemid, sgCount);
return true;
}
function generateRandomScroll() {
const InventoryType = Java.type('client.inventory.InventoryType');
if (cm.getPlayer().getInventory(InventoryType.USE).getNumFreeSlot() >= 1) {
var itemid = getRandomScroll(calculateScrollTiers());
if (itemid != -1) {
if (performExchange(itemid, 1)) {
cm.sendNext("Transaction accepted! You have received a #r#t" + itemid + "##k.");
} else {
cm.sendOk("Oh, it looks like some items are missing... Please double-check provided items in your inventory before trying to exchange.");
}
} else {
cm.sendOk("Sorry for the inconvenience, but it seems there are no scrolls on store right now... Try again later.");
}
} else {
cm.sendOk("Please look out for a slot available on your USE inventory before trying for a scroll.");
}
}

View File

@@ -50,8 +50,14 @@ function end(mode, type, selection) {
status++;
}
// TODO: there are 10 different riffs; quest2288/0 through quest2288/9.
// One of the riffs should play randomly upon the death of Spirit of Rock, but there is currently no system in place to achieve that in a reasonable way.
// Spirit of Rock (4300013) spawns an invisible mob on death (Spirit of Rock's Soul, 4300017) which was likely used in some clever way in GMS.
// The map (103040430) has two scripts which could be useful: onFirstUserEnter=Depart_Boss_F_Enter and onUserEnter=Depart_BossEnter
// Currently, the best hypothesis is that one of the map scripts registers some form of "mob spawn" action/script that runs once the invisible mob spawns.
// The script would randomly pick one of the 10 riffs and then register it with all chrs on the map (to later be used by this quest 2293) and play it.
if (status == 0) {
qm.sendSimple("Here, I'll give you some samples. Please listen to them and choose one. Please listen carefully before making your choide.\r\n\
qm.sendSimple("Here, I'll give you some samples. Please listen to them and choose one. Please listen carefully before making your choice.\r\n\
\t#b#L1# Listen to song No. 1#l \r\n\
\t#L2# Listen to Song No. 2#l \r\n\
\t#L3# Listen to Song No. 3#l \r\n\
@@ -67,7 +73,7 @@ function end(mode, type, selection) {
qm.sendOk("Was it this?");
status = -1;
} else if (selection == 3) {
qm.playSound("quest2293/Die");
qm.playSound("quest2288/6");
qm.sendOk("You heard that?");
status = -1;
} else if (selection == 4) {
@@ -88,4 +94,4 @@ function end(mode, type, selection) {
} else if (status == 3) {
qm.dispose();
}
}
}

View File

@@ -39,7 +39,7 @@ public abstract class AbstractCharacterObject extends AbstractAnimatedMapObject
protected MapleMap map;
protected int str, dex, luk, int_, hp, maxhp, mp, maxmp;
protected int hpMpApUsed, remainingAp;
protected int[] remainingSp = new int[10];
protected int[] remainingSp = new int[10]; // TODO: change to a simple int. Evan is not in v83, so why support it?
protected transient int clientmaxhp, clientmaxmp, localmaxhp = 50, localmaxmp = 5;
protected float transienthp = Float.NEGATIVE_INFINITY, transientmp = Float.NEGATIVE_INFINITY;

View File

@@ -21,6 +21,7 @@
*/
package client;
import model.CharacterIdentity;
import net.packet.Packet;
import net.server.PlayerStorage;
import tools.DatabaseConnection;
@@ -30,7 +31,12 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
public class BuddyList {
public enum BuddyOperation {
@@ -43,7 +49,7 @@ public class BuddyList {
private final Map<Integer, BuddylistEntry> buddies = new LinkedHashMap<>();
private int capacity;
private final Deque<CharacterNameAndId> pendingRequests = new LinkedList<>();
private final Deque<CharacterIdentity> pendingRequests = new LinkedList<>();
public BuddyList(int capacity) {
this.capacity = capacity;
@@ -145,7 +151,7 @@ public class BuddyList {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
if (rs.getInt("pending") == 1) {
pendingRequests.push(new CharacterNameAndId(rs.getInt("buddyid"), rs.getString("buddyname")));
pendingRequests.push(new CharacterIdentity(rs.getString("buddyname"), rs.getInt("buddyid")));
} else {
put(new BuddylistEntry(rs.getString("buddyname"), rs.getString("group"), rs.getInt("buddyid"), (byte) -1, true));
}
@@ -162,7 +168,7 @@ public class BuddyList {
}
}
public CharacterNameAndId pollPendingRequest() {
public CharacterIdentity pollPendingRequest() {
return pendingRequests.pollLast();
}
@@ -171,7 +177,7 @@ public class BuddyList {
if (pendingRequests.isEmpty()) {
c.sendPacket(PacketCreator.requestBuddylistAdd(cidFrom, c.getPlayer().getId(), nameFrom));
} else {
pendingRequests.push(new CharacterNameAndId(cidFrom, nameFrom));
pendingRequests.push(new CharacterIdentity(nameFrom, cidFrom));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
package client;
import lombok.Builder;
@Builder
public record CharacterStats(
int account,
int world,
String name,
int id,
int level,
int fame,
int str,
int dex,
int luk,
int int_,
int exp,
int gachaExp,
int hp,
int mp,
int maxHp,
int maxMp,
int sp,
int ap,
int gmLevel,
int skin,
int gender,
int job,
int hair,
int face,
int mapId,
int meso,
int hpMpApUsed,
int spawnPortal,
Integer party,
int buddyCapacity,
Integer messenger,
Integer messengerPosition,
Integer mountLevel,
Integer mountExp,
Integer mountTiredness,
int equipSlots,
int useSlots,
int setupSlots,
int etcSlots,
Integer monsterBookCover,
Integer dojoVanquisherStage,
int dojoPoints,
Integer dojoStage,
boolean dojoTutorialComplete,
int dojoVanquisherKills,
int matchCardWins,
int matchCardLosses,
int matchCardTies,
int omokWins,
int omokLosses,
int omokTies,
String dataString,
Long jailExpiration,
Integer partnerId,
Integer marriageItemId,
Long lastExpGainTime,
int ariantPoints,
boolean canRecvPartySearchInvite
) {
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
package client;
/**
* @author Ponk
*/
public class Gender {
public static final byte MALE = 0;
public static final byte FEMALE = 1;
public static final byte NOT_SET = 10;
}

View File

@@ -0,0 +1,29 @@
package client;
import java.util.Arrays;
import java.util.Optional;
/**
* @author Ponk
*/
public enum LoginState {
LOGGED_OUT(0),
SERVER_TRANSITION(1),
LOGGED_IN(2);
private final byte value;
LoginState(int value) {
this.value = (byte) value;
}
public byte getValue() {
return value;
}
public static Optional<LoginState> fromValue(int value) {
return Arrays.stream(values())
.filter(v -> v.getValue() == value)
.findFirst();
}
}

View File

@@ -1,221 +1,110 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client;
import tools.DatabaseConnection;
import database.monsterbook.MonsterCard;
import net.jcip.annotations.ThreadSafe;
import tools.PacketCreator;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public final class MonsterBook {
private int specialCard = 0;
private int normalCard = 0;
private int bookLevel = 1;
private final Map<Integer, Integer> cards = new LinkedHashMap<>();
private final Lock lock = new ReentrantLock();
// TODO: add tests
@ThreadSafe
public class MonsterBook {
private final Map<Integer, MonsterCard> cards;
private int bookLevel;
public Set<Entry<Integer, Integer>> getCardSet() {
lock.lock();
try {
return new HashSet<>(cards.entrySet());
} finally {
lock.unlock();
}
public MonsterBook(List<MonsterCard> monsterCards) {
this.cards = monsterCards.stream()
.collect(Collectors.toMap(MonsterCard::cardId, Function.identity()));
}
public void addCard(final Client c, final int cardid) {
c.getPlayer().getMap().broadcastMessage(c.getPlayer(), PacketCreator.showForeignCardEffect(c.getPlayer().getId()), false);
public synchronized List<MonsterCard> getCards() {
return new ArrayList<>(cards.values());
}
Integer qty;
lock.lock();
try {
qty = cards.get(cardid);
if (qty != null) {
if (qty < 5) {
cards.put(cardid, qty + 1);
}
} else {
cards.put(cardid, 1);
qty = 0;
if (cardid / 1000 >= 2388) {
specialCard++;
} else {
normalCard++;
}
}
} finally {
lock.unlock();
public synchronized void addCard(int cardId, Client client) {
var monsterCard = cards.get(cardId);
if (monsterCard != null && monsterCard.isMaxLevel()) {
client.sendPacket(PacketCreator.addMonsterCardAlreadyFull());
return;
}
if (qty < 5) {
if (qty == 0) { // leveling system only accounts unique cards
calculateLevel();
}
c.sendPacket(PacketCreator.addCard(false, cardid, qty + 1));
c.sendPacket(PacketCreator.showGainCard());
boolean isNewCard = monsterCard == null;
final MonsterCard card;
if (isNewCard) {
card = new MonsterCard(cardId, (byte) 1);
cards.put(cardId, card);
calculateAndSetLevel();
} else {
c.sendPacket(PacketCreator.addCard(true, cardid, 5));
}
}
private void calculateLevel() {
lock.lock();
try {
int collectionExp = (normalCard + specialCard);
int level = 0, expToNextlevel = 1;
do {
level++;
expToNextlevel += level * 10;
} while (collectionExp >= expToNextlevel);
bookLevel = level; // thanks IxianMace for noticing book level differing between book UI and character info UI
} finally {
lock.unlock();
}
}
public int getBookLevel() {
lock.lock();
try {
return bookLevel;
} finally {
lock.unlock();
}
}
public Map<Integer, Integer> getCards() {
lock.lock();
try {
return Collections.unmodifiableMap(cards);
} finally {
lock.unlock();
}
}
public int getTotalCards() {
lock.lock();
try {
return specialCard + normalCard;
} finally {
lock.unlock();
}
}
public int getNormalCard() {
lock.lock();
try {
return normalCard;
} finally {
lock.unlock();
}
}
public int getSpecialCard() {
lock.lock();
try {
return specialCard;
} finally {
lock.unlock();
}
}
public void loadCards(final int charid) throws SQLException {
lock.lock();
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT cardid, level FROM monsterbook WHERE charid = ? ORDER BY cardid ASC")) {
ps.setInt(1, charid);
try (ResultSet rs = ps.executeQuery()) {
int cardid;
int level;
while (rs.next()) {
cardid = rs.getInt("cardid");
level = rs.getInt("level");
if (cardid / 1000 >= 2388) {
specialCard++;
} else {
normalCard++;
}
cards.put(cardid, level);
}
}
} finally {
lock.unlock();
card = new MonsterCard(cardId, (byte) (monsterCard.level() + 1));
cards.put(cardId, card);
}
calculateLevel();
var chr = client.getPlayer();
chr.sendPacket(PacketCreator.addMonsterCard(card));
chr.sendPacket(PacketCreator.showMonsterCardEffect());
chr.getMap().broadcastMessage(chr, PacketCreator.showForeignMonsterCardEffect(chr.getId()), false);
}
public void saveCards(Connection con, int chrId) throws SQLException {
private synchronized void calculateAndSetLevel() {
int collectionExp = getTotalCards();
int level = 0;
int expToNextLevel = 1;
do {
level++;
expToNextLevel += level * 10;
} while (collectionExp >= expToNextLevel);
this.bookLevel = level;
}
public synchronized int getBookLevel() {
return bookLevel;
}
public synchronized int getNormalCards() {
return (int) cards.values().stream()
.filter(Predicate.not(MonsterCard::isSpecial))
.count();
}
public synchronized int getSpecialCards() {
return (int) cards.values().stream()
.filter(MonsterCard::isSpecial)
.count();
}
public synchronized int getTotalCards() {
return cards.size();
}
public synchronized void saveCards(Connection con, int chrId) throws SQLException {
final String query = """
INSERT INTO monsterbook (charid, cardid, level)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE level = ?;
""";
try (final PreparedStatement ps = con.prepareStatement(query)) {
for (Map.Entry<Integer, Integer> cardAndLevel : cards.entrySet()) {
final int card = cardAndLevel.getKey();
final int level = cardAndLevel.getValue();
for (MonsterCard card : cards.values()) {
// insert
ps.setInt(1, chrId);
ps.setInt(2, card);
ps.setInt(3, level);
ps.setInt(2, card.cardId());
ps.setInt(3, card.level());
// update
ps.setInt(4, level);
ps.setInt(4, card.level());
ps.addBatch();
}
ps.executeBatch();
}
}
public static int[] getCardTierSize() {
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM monstercarddata GROUP BY floor(cardid / 1000);", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = ps.executeQuery()) {
rs.last();
int[] tierSizes = new int[rs.getRow()];
rs.beforeFirst();
while (rs.next()) {
tierSizes[rs.getRow() - 1] = rs.getInt(1);
}
return tierSizes;
} catch (SQLException e) {
e.printStackTrace();
return new int[0];
}
}
}

View File

@@ -24,7 +24,11 @@ package client;
import server.quest.Quest;
import tools.StringUtil;
import java.util.*;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author Matze

View File

@@ -21,8 +21,63 @@
*/
package client;
import constants.skills.*;
import provider.*;
import constants.skills.Aran;
import constants.skills.Archer;
import constants.skills.Assassin;
import constants.skills.Bandit;
import constants.skills.Beginner;
import constants.skills.Bishop;
import constants.skills.BlazeWizard;
import constants.skills.Bowmaster;
import constants.skills.Buccaneer;
import constants.skills.ChiefBandit;
import constants.skills.Cleric;
import constants.skills.Corsair;
import constants.skills.Crossbowman;
import constants.skills.Crusader;
import constants.skills.DarkKnight;
import constants.skills.DawnWarrior;
import constants.skills.DragonKnight;
import constants.skills.Evan;
import constants.skills.FPArchMage;
import constants.skills.FPMage;
import constants.skills.FPWizard;
import constants.skills.Fighter;
import constants.skills.GM;
import constants.skills.Gunslinger;
import constants.skills.Hermit;
import constants.skills.Hero;
import constants.skills.Hunter;
import constants.skills.ILArchMage;
import constants.skills.ILMage;
import constants.skills.ILWizard;
import constants.skills.Legend;
import constants.skills.Magician;
import constants.skills.Marauder;
import constants.skills.Marksman;
import constants.skills.NightLord;
import constants.skills.NightWalker;
import constants.skills.Noblesse;
import constants.skills.Page;
import constants.skills.Paladin;
import constants.skills.Pirate;
import constants.skills.Priest;
import constants.skills.Ranger;
import constants.skills.Rogue;
import constants.skills.Shadower;
import constants.skills.Sniper;
import constants.skills.Spearman;
import constants.skills.SuperGM;
import constants.skills.ThunderBreaker;
import constants.skills.Warrior;
import constants.skills.WhiteKnight;
import constants.skills.WindArcher;
import provider.Data;
import provider.DataDirectoryEntry;
import provider.DataFileEntry;
import provider.DataProvider;
import provider.DataProviderFactory;
import provider.DataTool;
import provider.wz.WZFiles;
import server.StatEffect;
import server.life.Element;

View File

@@ -22,14 +22,15 @@
package client;
public enum SkinColor {
NORMAL(0),
DARK(1),
BLACK(2),
LIGHT(0),
TANNED(1),
DARK(2),
PALE(3),
BLUE(4),
GREEN(5),
WHITE(9),
PINK(10);
PINK(10),
BROWN(11);
final int id;

View File

@@ -88,10 +88,6 @@ public enum AutobanFactory {
return expiretime;
}
public void addPoint(AutobanManager ban, String reason) {
ban.addPoint(this, reason);
}
public void alert(Character chr, String reason) {
if (YamlConfig.config.server.USE_AUTOBAN) {
if (chr != null && isIgnored(chr.getId())) {
@@ -105,13 +101,6 @@ public enum AutobanFactory {
}
}
public void autoban(Character chr, String value) {
if (YamlConfig.config.server.USE_AUTOBAN) {
chr.autoban("Autobanned for (" + this.name() + ": " + value + ")");
//chr.sendPolice("You will be disconnected for (" + this.name() + ": " + value + ")");
}
}
/**
* Toggle ignored status for a character id.
* An ignored character will not trigger GM alerts.

View File

@@ -7,6 +7,7 @@ package client.autoban;
import client.Character;
import config.YamlConfig;
import net.netty.GameViolationException;
import net.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,35 +36,27 @@ public class AutobanManager {
this.chr = chr;
}
public void addPoint(AutobanFactory fac, String reason) {
if (YamlConfig.config.server.USE_AUTOBAN) {
if (chr.isGM() || chr.isBanned()) {
return;
}
if (lastTime.containsKey(fac)) {
if (lastTime.get(fac) < (Server.getInstance().getCurrentTime() - fac.getExpire())) {
points.put(fac, points.get(fac) / 2); //So the points are not completely gone.
}
}
if (fac.getExpire() != -1) {
lastTime.put(fac, Server.getInstance().getCurrentTime());
}
if (points.containsKey(fac)) {
points.put(fac, points.get(fac) + 1);
} else {
points.put(fac, 1);
}
if (points.get(fac) >= fac.getMaximum()) {
chr.autoban(reason);
/**
* @return true if the added point should result in an autoban
*/
public boolean addPoint(AutobanFactory fac) {
if (lastTime.containsKey(fac)) {
if (lastTime.get(fac) < (Server.getInstance().getCurrentTime() - fac.getExpire())) {
points.put(fac, points.get(fac) / 2); //So the points are not completely gone.
}
}
if (YamlConfig.config.server.USE_AUTOBAN_LOG) {
// Lets log every single point too.
log.info("Autoban - chr {} caused {} {}", Character.makeMapleReadable(chr.getName()), fac.name(), reason);
if (fac.getExpire() != -1) {
lastTime.put(fac, Server.getInstance().getCurrentTime());
}
if (points.containsKey(fac)) {
points.put(fac, points.get(fac) + 1);
} else {
points.put(fac, 1);
}
return points.get(fac) >= fac.getMaximum();
}
public void addMiss() {
@@ -118,11 +111,10 @@ public class AutobanManager {
if (this.timestamp[type] == time) {
this.timestampcounter[type]++;
if (this.timestampcounter[type] >= times) {
if (YamlConfig.config.server.USE_AUTOBAN) {
chr.getClient().disconnect(false, false);
}
log.info("Autoban - Chr {} was caught spamming TYPE {} and has been disconnected", chr, type);
if (YamlConfig.config.server.USE_AUTOBAN) {
throw new GameViolationException("Auto ban");
}
}
} else {
this.timestamp[type] = time;

View File

@@ -30,7 +30,7 @@ public abstract class Command {
protected int rank;
protected String description;
public abstract void execute(Client client, String[] params);
public abstract void execute(Client client, String[] params, CommandContext ctx);
public String getDescription() {
return description;

View File

@@ -0,0 +1,19 @@
package client.command;
import database.character.CharacterSaver;
import database.drop.DropProvider;
import server.shop.ShopFactory;
import service.BanService;
import service.TransitionService;
/**
* @author Ponk
*/
public record CommandContext(CommandsExecutor commandsExecutor, DropProvider dropProvider, ShopFactory shopFactory,
CharacterSaver characterSaver, TransitionService transitionService, BanService banService
) {
public CommandContext with(CommandsExecutor ce) {
return new CommandContext(ce, dropProvider, shopFactory, characterSaver, transitionService, banService);
}
}

View File

@@ -24,13 +24,171 @@
package client.command;
import client.Client;
import client.command.commands.gm0.*;
import client.command.commands.gm1.*;
import client.command.commands.gm2.*;
import client.command.commands.gm3.*;
import client.command.commands.gm4.*;
import client.command.commands.gm5.*;
import client.command.commands.gm6.*;
import client.command.commands.gm0.DisposeCommand;
import client.command.commands.gm0.DropLimitCommand;
import client.command.commands.gm0.EnableAuthCommand;
import client.command.commands.gm0.EquipLvCommand;
import client.command.commands.gm0.GachaCommand;
import client.command.commands.gm0.GmCommand;
import client.command.commands.gm0.HelpCommand;
import client.command.commands.gm0.JoinEventCommand;
import client.command.commands.gm0.LeaveEventCommand;
import client.command.commands.gm0.MapOwnerClaimCommand;
import client.command.commands.gm0.OnlineCommand;
import client.command.commands.gm0.RanksCommand;
import client.command.commands.gm0.RatesCommand;
import client.command.commands.gm0.ReportBugCommand;
import client.command.commands.gm0.ShowRatesCommand;
import client.command.commands.gm0.StaffCommand;
import client.command.commands.gm0.StatDexCommand;
import client.command.commands.gm0.StatIntCommand;
import client.command.commands.gm0.StatLukCommand;
import client.command.commands.gm0.StatStrCommand;
import client.command.commands.gm0.TimeCommand;
import client.command.commands.gm0.ToggleExpCommand;
import client.command.commands.gm0.UptimeCommand;
import client.command.commands.gm1.BossHpCommand;
import client.command.commands.gm1.BuffMeCommand;
import client.command.commands.gm1.GotoCommand;
import client.command.commands.gm1.MobHpCommand;
import client.command.commands.gm1.WhatDropsFromCommand;
import client.command.commands.gm1.WhoDropsCommand;
import client.command.commands.gm2.ApCommand;
import client.command.commands.gm2.BombCommand;
import client.command.commands.gm2.BuffCommand;
import client.command.commands.gm2.BuffMapCommand;
import client.command.commands.gm2.ClearDropsCommand;
import client.command.commands.gm2.ClearSavedLocationsCommand;
import client.command.commands.gm2.ClearSlotCommand;
import client.command.commands.gm2.DcCommand;
import client.command.commands.gm2.EmpowerMeCommand;
import client.command.commands.gm2.GachaListCommand;
import client.command.commands.gm2.GmShopCommand;
import client.command.commands.gm2.HealCommand;
import client.command.commands.gm2.HideCommand;
import client.command.commands.gm2.IdCommand;
import client.command.commands.gm2.ItemCommand;
import client.command.commands.gm2.ItemDropCommand;
import client.command.commands.gm2.JailCommand;
import client.command.commands.gm2.JobCommand;
import client.command.commands.gm2.LevelCommand;
import client.command.commands.gm2.LevelProCommand;
import client.command.commands.gm2.LootCommand;
import client.command.commands.gm2.MaxSkillCommand;
import client.command.commands.gm2.MaxStatCommand;
import client.command.commands.gm2.MobSkillCommand;
import client.command.commands.gm2.ReachCommand;
import client.command.commands.gm2.RechargeCommand;
import client.command.commands.gm2.ResetSkillCommand;
import client.command.commands.gm2.SearchCommand;
import client.command.commands.gm2.SetSlotCommand;
import client.command.commands.gm2.SetStatCommand;
import client.command.commands.gm2.SpCommand;
import client.command.commands.gm2.SummonCommand;
import client.command.commands.gm2.UnBugCommand;
import client.command.commands.gm2.UnHideCommand;
import client.command.commands.gm2.UnJailCommand;
import client.command.commands.gm2.WarpAreaCommand;
import client.command.commands.gm2.WarpCommand;
import client.command.commands.gm2.WarpMapCommand;
import client.command.commands.gm2.WhereaMiCommand;
import client.command.commands.gm3.BanCommand;
import client.command.commands.gm3.ChatCommand;
import client.command.commands.gm3.CheckDmgCommand;
import client.command.commands.gm3.ClosePortalCommand;
import client.command.commands.gm3.DebuffCommand;
import client.command.commands.gm3.EndEventCommand;
import client.command.commands.gm3.ExpedsCommand;
import client.command.commands.gm3.FaceCommand;
import client.command.commands.gm3.FameCommand;
import client.command.commands.gm3.FlyCommand;
import client.command.commands.gm3.GiveMesosCommand;
import client.command.commands.gm3.GiveNxCommand;
import client.command.commands.gm3.HairCommand;
import client.command.commands.gm3.HealMapCommand;
import client.command.commands.gm3.HealPersonCommand;
import client.command.commands.gm3.HpMpCommand;
import client.command.commands.gm3.HurtCommand;
import client.command.commands.gm3.IgnoreCommand;
import client.command.commands.gm3.IgnoredCommand;
import client.command.commands.gm3.InMapCommand;
import client.command.commands.gm3.KillAllCommand;
import client.command.commands.gm3.KillCommand;
import client.command.commands.gm3.KillMapCommand;
import client.command.commands.gm3.MaxEnergyCommand;
import client.command.commands.gm3.MaxHpMpCommand;
import client.command.commands.gm3.MonitorCommand;
import client.command.commands.gm3.MonitorsCommand;
import client.command.commands.gm3.MusicCommand;
import client.command.commands.gm3.MuteMapCommand;
import client.command.commands.gm3.NightCommand;
import client.command.commands.gm3.NoticeCommand;
import client.command.commands.gm3.NpcCommand;
import client.command.commands.gm3.OnlineTwoCommand;
import client.command.commands.gm3.OpenPortalCommand;
import client.command.commands.gm3.PeCommand;
import client.command.commands.gm3.PosCommand;
import client.command.commands.gm3.QuestCompleteCommand;
import client.command.commands.gm3.QuestResetCommand;
import client.command.commands.gm3.QuestStartCommand;
import client.command.commands.gm3.ReloadDropsCommand;
import client.command.commands.gm3.ReloadEventsCommand;
import client.command.commands.gm3.ReloadMapCommand;
import client.command.commands.gm3.ReloadPortalsCommand;
import client.command.commands.gm3.ReloadShopsCommand;
import client.command.commands.gm3.RipCommand;
import client.command.commands.gm3.SeedCommand;
import client.command.commands.gm3.SpawnCommand;
import client.command.commands.gm3.StartEventCommand;
import client.command.commands.gm3.StartMapEventCommand;
import client.command.commands.gm3.StopMapEventCommand;
import client.command.commands.gm3.TimerAllCommand;
import client.command.commands.gm3.TimerCommand;
import client.command.commands.gm3.TimerMapCommand;
import client.command.commands.gm3.ToggleCouponCommand;
import client.command.commands.gm3.UnBanCommand;
import client.command.commands.gm4.BossDropRateCommand;
import client.command.commands.gm4.CakeCommand;
import client.command.commands.gm4.DropRateCommand;
import client.command.commands.gm4.ExpRateCommand;
import client.command.commands.gm4.FishingRateCommand;
import client.command.commands.gm4.ForceVacCommand;
import client.command.commands.gm4.HorntailCommand;
import client.command.commands.gm4.ItemVacCommand;
import client.command.commands.gm4.MesoRateCommand;
import client.command.commands.gm4.PapCommand;
import client.command.commands.gm4.PianusCommand;
import client.command.commands.gm4.PinkbeanCommand;
import client.command.commands.gm4.PlayerNpcCommand;
import client.command.commands.gm4.PlayerNpcRemoveCommand;
import client.command.commands.gm4.PmobCommand;
import client.command.commands.gm4.PmobRemoveCommand;
import client.command.commands.gm4.PnpcCommand;
import client.command.commands.gm4.PnpcRemoveCommand;
import client.command.commands.gm4.ProItemCommand;
import client.command.commands.gm4.QuestRateCommand;
import client.command.commands.gm4.ServerMessageCommand;
import client.command.commands.gm4.SetEqStatCommand;
import client.command.commands.gm4.TravelRateCommand;
import client.command.commands.gm4.ZakumCommand;
import client.command.commands.gm5.DebugCommand;
import client.command.commands.gm5.IpListCommand;
import client.command.commands.gm5.SetCommand;
import client.command.commands.gm5.ShowMoveLifeCommand;
import client.command.commands.gm5.ShowPacketsCommand;
import client.command.commands.gm5.ShowSessionsCommand;
import client.command.commands.gm6.ClearQuestCacheCommand;
import client.command.commands.gm6.ClearQuestCommand;
import client.command.commands.gm6.DCAllCommand;
import client.command.commands.gm6.DevtestCommand;
import client.command.commands.gm6.EraseAllPNpcsCommand;
import client.command.commands.gm6.GetAccCommand;
import client.command.commands.gm6.MapPlayersCommand;
import client.command.commands.gm6.SaveAllCommand;
import client.command.commands.gm6.SetGmLevelCommand;
import client.command.commands.gm6.ShutdownCommand;
import client.command.commands.gm6.SpawnAllPNpcsCommand;
import client.command.commands.gm6.SupplyRateCouponCommand;
import constants.id.MapId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -43,16 +201,27 @@ import java.util.List;
public class CommandsExecutor {
private static final Logger log = LoggerFactory.getLogger(CommandsExecutor.class);
private static final CommandsExecutor instance = new CommandsExecutor();
private static final char USER_HEADING = '@';
private static final char GM_HEADING = '!';
private final HashMap<String, Command> registeredCommands = new HashMap<>();
private final List<Pair<List<String>, List<String>>> commandsNameDesc = new ArrayList<>();
private final CommandContext commandContext;
private Pair<List<String>, List<String>> levelCommandsCursor;
public static CommandsExecutor getInstance() {
return instance;
public CommandsExecutor(CommandContext commandContext) {
this.commandContext = commandContext.with(this);
registerCommands();
}
private void registerCommands() {
registerLv0Commands();
registerLv1Commands();
registerLv2Commands();
registerLv3Commands();
registerLv4Commands();
registerLv5Commands();
registerLv6Commands();
}
public static boolean isCommand(Client client, String content) {
@@ -63,16 +232,6 @@ public class CommandsExecutor {
return heading == USER_HEADING;
}
private CommandsExecutor() {
registerLv0Commands();
registerLv1Commands();
registerLv2Commands();
registerLv3Commands();
registerLv4Commands();
registerLv5Commands();
registerLv6Commands();
}
public List<Pair<List<String>, List<String>>> getGmCommands() {
return commandsNameDesc;
}
@@ -120,7 +279,7 @@ public class CommandsExecutor {
params = new String[]{};
}
command.execute(client, params);
command.execute(client, params, commandContext);
log.info("Chr {} used command {}", client.getPlayer().getName(), command.getClass().getSimpleName());
}
@@ -177,18 +336,15 @@ public class CommandsExecutor {
addCommand("droplimit", DropLimitCommand.class);
addCommand("time", TimeCommand.class);
addCommand("credits", StaffCommand.class);
addCommand("buyback", BuyBackCommand.class);
addCommand("uptime", UptimeCommand.class);
addCommand("gacha", GachaCommand.class);
addCommand("dispose", DisposeCommand.class);
addCommand("changel", ChangeLanguageCommand.class);
addCommand("equiplv", EquipLvCommand.class);
addCommand("showrates", ShowRatesCommand.class);
addCommand("rates", RatesCommand.class);
addCommand("online", OnlineCommand.class);
addCommand("gm", GmCommand.class);
addCommand("reportbug", ReportBugCommand.class);
addCommand("points", ReadPointsCommand.class);
addCommand("joinevent", JoinEventCommand.class);
addCommand("leaveevent", LeaveEventCommand.class);
addCommand("ranks", RanksCommand.class);
@@ -288,9 +444,7 @@ public class CommandsExecutor {
addCommand("togglewhitechat", 3, ChatCommand.class);
addCommand("fame", 3, FameCommand.class);
addCommand("givenx", 3, GiveNxCommand.class);
addCommand("givevp", 3, GiveVpCommand.class);
addCommand("givems", 3, GiveMesosCommand.class);
addCommand("giverp", 3, GiveRpCommand.class);
addCommand("expeds", 3, ExpedsCommand.class);
addCommand("kill", 3, KillCommand.class);
addCommand("seed", 3, SeedCommand.class);
@@ -376,7 +530,6 @@ public class CommandsExecutor {
levelCommandsCursor = new Pair<>(new ArrayList<String>(), new ArrayList<String>());
addCommand("setgmlevel", 6, SetGmLevelCommand.class);
addCommand("warpworld", 6, WarpWorldCommand.class);
addCommand("saveall", 6, SaveAllCommand.class);
addCommand("dcall", 6, DCAllCommand.class);
addCommand("mapplayers", 6, MapPlayersCommand.class);
@@ -387,10 +540,7 @@ public class CommandsExecutor {
addCommand("supplyratecoupon", 6, SupplyRateCouponCommand.class);
addCommand("spawnallpnpcs", 6, SpawnAllPNpcsCommand.class);
addCommand("eraseallpnpcs", 6, EraseAllPNpcsCommand.class);
addCommand("addchannel", 6, ServerAddChannelCommand.class);
addCommand("addworld", 6, ServerAddWorldCommand.class);
addCommand("removechannel", 6, ServerRemoveChannelCommand.class);
addCommand("removeworld", 6, ServerRemoveWorldCommand.class);
addCommand("devtest", 6, DevtestCommand.class);
commandsNameDesc.add(levelCommandsCursor);
}

View File

@@ -1,48 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server, commands OdinMS-based
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
@Author: Arthur L - Refactored command content into modules
*/
package client.command.commands.gm0;
import client.Client;
import client.command.Command;
import client.processor.action.BuybackProcessor;
public class BuyBackCommand extends Command {
{
setDescription("Revive yourself after a death.");
}
@Override
public void execute(Client c, String[] params) {
if (params.length < 1) {
c.getPlayer().yellowMessage("Syntax: @buyback <info|now>");
return;
}
if (params[0].contentEquals("now")) {
BuybackProcessor.processBuyback(c);
} else {
c.getPlayer().showBuybackInfo();
}
}
}

View File

@@ -1,42 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server, commands OdinMS-based
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
@Author: Arthur L - Refactored command content into modules
*/
package client.command.commands.gm0;
import client.Client;
import client.command.Command;
public class ChangeLanguageCommand extends Command {
{
setDescription("Change language settings.");
}
@Override
public void execute(Client c, String[] params) {
if (params.length < 1) {
c.getPlayer().yellowMessage("Syntax: !changel <0=ptb, 1=esp, 2=eng>");
return;
}
c.setLanguage(Integer.parseInt(params[0]));
}
}

View File

@@ -25,6 +25,7 @@ package client.command.commands.gm0;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import scripting.npc.NPCScriptManager;
import scripting.quest.QuestScriptManager;
import tools.PacketCreator;
@@ -35,7 +36,7 @@ public class DisposeCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
NPCScriptManager.getInstance().dispose(c);
QuestScriptManager.getInstance().dispose(c);
c.sendPacket(PacketCreator.enableActions());

View File

@@ -25,6 +25,7 @@ package client.command.commands.gm0;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import config.YamlConfig;
public class DropLimitCommand extends Command {
@@ -33,7 +34,7 @@ public class DropLimitCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
int dropCount = c.getPlayer().getMap().getDroppedItemCount();
if (((float) dropCount) / YamlConfig.config.server.ITEM_LIMIT_ON_MAP < 0.75f) {
c.getPlayer().showHint("Current drop count: #b" + dropCount + "#k / #e" + YamlConfig.config.server.ITEM_LIMIT_ON_MAP + "#n", 300);

View File

@@ -25,6 +25,7 @@ package client.command.commands.gm0;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import net.server.coordinator.login.LoginBypassCoordinator;
public class EnableAuthCommand extends Command {
@@ -33,7 +34,7 @@ public class EnableAuthCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
if (c.tryacquireClient()) {
try {
LoginBypassCoordinator.getInstance().unregisterLoginBypassEntry(c.getHwid(), c.getAccID());

View File

@@ -25,6 +25,7 @@ package client.command.commands.gm0;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
public class EquipLvCommand extends Command {
{
@@ -32,7 +33,7 @@ public class EquipLvCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
c.getPlayer().showAllEquipFeatures();
}
}

View File

@@ -25,6 +25,7 @@ package client.command.commands.gm0;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import constants.id.NpcId;
import server.ItemInformationProvider;
import server.gachapon.Gachapon;
@@ -35,7 +36,7 @@ public class GachaCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Gachapon.GachaponType gacha = null;
String search = c.getPlayer().getLastCommandMessage();
String gachaName = "";

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import net.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,7 +41,7 @@ public class GmCommand extends Command {
private static final Logger log = LoggerFactory.getLogger(GmCommand.class);
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
String[] tips = {
"Please only use @gm in emergencies or to report somebody.",
"To report a bug or make a suggestion, use the forum.",

View File

@@ -25,15 +25,19 @@ package client.command.commands.gm0;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import constants.id.NpcId;
import java.util.Map;
public class HelpCommand extends Command {
{
setDescription("Show available commands.");
}
@Override
public void execute(Client client, String[] params) {
client.getAbstractPlayerInteraction().openNpc(NpcId.STEWARD, "commands");
public void execute(Client client, String[] params, CommandContext ctx) {
Map<String, Object> bindings = Map.of("ce", ctx.commandsExecutor());
client.getAbstractPlayerInteraction().openNpc(NpcId.STEWARD, "commands", bindings);
}
}

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import constants.id.MapId;
import server.events.gm.Event;
import server.maps.FieldLimit;
@@ -36,7 +37,7 @@ public class JoinEventCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
if (!FieldLimit.CANNOTMIGRATE.check(player.getMap().getFieldLimit())) {
Event event = c.getChannelServer().getEvent();

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
public class LeaveEventCommand extends Command {
{
@@ -33,7 +34,7 @@ public class LeaveEventCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
int returnMap = player.getSavedLocation("EVENT");
if (returnMap != -1) {

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import config.YamlConfig;
import server.maps.MapleMap;
@@ -35,7 +36,7 @@ public class MapOwnerClaimCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
if (c.tryacquireClient()) {
try {
Character chr = c.getPlayer();

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import net.server.Server;
import net.server.channel.Channel;
@@ -35,7 +36,7 @@ public class OnlineCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
for (Channel ch : Server.getInstance().getChannelsFromWorld(player.getWorld())) {
player.yellowMessage("Players in Channel " + ch.getId() + ":");

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import constants.id.NpcId;
import net.server.Server;
import net.server.guild.GuildPackets;
@@ -39,7 +40,7 @@ public class RanksCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
List<Pair<String, Integer>> worldRanking = Server.getInstance().getWorldPlayerRanking(player.getWorld());

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import config.YamlConfig;
public class RatesCommand extends Command {
@@ -34,7 +35,7 @@ public class RatesCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
// travel rates not applicable since it's intrinsically a server/environment rate rather than a character rate

View File

@@ -1,38 +0,0 @@
package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
public class ReadPointsCommand extends Command {
{
setDescription("Show point total.");
}
@Override
public void execute(Client client, String[] params) {
Character player = client.getPlayer();
if (params.length > 2) {
player.yellowMessage("Syntax: @points (rp|vp|all)");
return;
} else if (params.length == 0) {
player.yellowMessage("RewardPoints: " + player.getRewardPoints() + " | "
+ "VotePoints: " + player.getClient().getVotePoints());
return;
}
switch (params[0]) {
case "rp":
player.yellowMessage("RewardPoints: " + player.getRewardPoints());
break;
case "vp":
player.yellowMessage("VotePoints: " + player.getClient().getVotePoints());
break;
default:
player.yellowMessage("RewardPoints: " + player.getRewardPoints() + " | "
+ "VotePoints: " + player.getClient().getVotePoints());
break;
}
}
}

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import net.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,7 +40,7 @@ public class ReportBugCommand extends Command {
private static final Logger log = LoggerFactory.getLogger(ReportBugCommand.class);
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
if (params.length < 1) {

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import config.YamlConfig;
public class ShowRatesCommand extends Command {
@@ -34,7 +35,7 @@ public class ShowRatesCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
String showMsg = "#eEXP RATE#n" + "\r\n";
showMsg += "World EXP Rate: #k" + c.getWorldServer().getExpRate() + "x#k" + "\r\n";

View File

@@ -25,6 +25,7 @@ package client.command.commands.gm0;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import constants.id.NpcId;
public class StaffCommand extends Command {
@@ -33,7 +34,7 @@ public class StaffCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
c.getAbstractPlayerInteraction().openNpc(NpcId.HERACLE, "credits");
}
}

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import config.YamlConfig;
public class StatDexCommand extends Command {
@@ -34,7 +35,7 @@ public class StatDexCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
int remainingAp = player.getRemainingAp();

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import config.YamlConfig;
public class StatIntCommand extends Command {
@@ -34,7 +35,7 @@ public class StatIntCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
int remainingAp = player.getRemainingAp();

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import config.YamlConfig;
public class StatLukCommand extends Command {
@@ -34,7 +35,7 @@ public class StatLukCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
int remainingAp = player.getRemainingAp();

View File

@@ -26,6 +26,7 @@ package client.command.commands.gm0;
import client.Character;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import config.YamlConfig;
public class StatStrCommand extends Command {
@@ -34,7 +35,7 @@ public class StatStrCommand extends Command {
}
@Override
public void execute(Client c, String[] params) {
public void execute(Client c, String[] params, CommandContext ctx) {
Character player = c.getPlayer();
int remainingAp = player.getRemainingAp();
int amount;

View File

@@ -25,6 +25,7 @@ package client.command.commands.gm0;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@@ -37,7 +38,7 @@ public class TimeCommand extends Command {
}
@Override
public void execute(Client client, String[] params) {
public void execute(Client client, String[] params, CommandContext ctx) {
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getDefault());
client.getPlayer().yellowMessage("Cosmic Server Time: " + dateFormat.format(new Date()));

Some files were not shown because too many files have changed in this diff Show More