Compare commits

..

82 Commits

Author SHA1 Message Date
wincent
328743f1e1 Local work 2024-09-08 17:40:32 +02:00
wincent
3178a702a7 Updated how skills and upgrades work, more gui stuff 2024-07-17 20:59:11 +02:00
wincent
5e3312850c Refactor damage calculations and add additional modifiers 2024-07-06 00:02:30 +02:00
wincent
756dc4e44f Updated how upgrade adds skills 2024-06-08 17:31:22 +02:00
wincent
87613f287f Fix g++ 14 2024-06-06 09:37:49 +02:00
wincent
ac1b00fdaa Updates to upgrades 2024-06-06 09:37:24 +02:00
wincent
364bcf822a Added upgrades 2024-06-02 15:43:35 +02:00
wincent
d5b2278dc5 Refactor and combat changes 2024-06-02 09:53:56 +02:00
wincent
0bf5ee02e4 Merge remote-tracking branch 'origin/main' into nejlika 2024-05-30 10:56:58 +02:00
wincent
c2ad76ee66 Moved to a dedicated nejlika fle 2024-05-30 10:54:38 +02:00
Remco Hofman
cce5755366 Fix Dockerfile vanity COPY (#1604)
Corrected an unintended mistake in the COPY commands for adding the
vanity files to the Docker container, causing only the last file
contents to be added to the file '/app/vanity/*'
2024-05-27 17:46:09 -07:00
TAHuntling
e966d3a644 Chore: split VE script up (#1598)
* Testing Scripts

Testing splitting AgSpaceStuff into AgSpaceStuff and AgShipShake

* fixed inclusions

* Removed DoShake

* cleaning up

* consistent if statements

* Update dScripts/ai/AG/AgShipShake.h

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

---------

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
2024-05-27 01:24:48 -05:00
Gie "Max" Vanommeslaeghe
9328021339 Merge pull request #1600 from DarkflameUniverse/add-missing-scripts
fix: add back missing scripts from scripts refactor
2024-05-26 12:02:12 +02:00
Gie "Max" Vanommeslaeghe
d1134fdd62 Merge pull request #1601 from DarkflameUniverse/fix-using-skill-in-race
fix: players using non-car skills in a race
2024-05-26 12:01:54 +02:00
David Markowitz
efa658bc31 fix players using non-car skills in a race 2024-05-25 19:59:15 -07:00
David Markowitz
e59525d2ae Update CppScripts.cpp 2024-05-25 19:32:18 -07:00
wincent
c6528d444a Merge branch 'observable' into nejlika 2024-05-25 19:26:49 +02:00
David Markowitz
0348db72a5 fiux mission (#1596) 2024-05-25 12:24:02 -05:00
wincent
78425aacb1 Proposal for observers and deferred implementations 2024-05-25 19:13:22 +02:00
TAHuntling
debc2a96e2 Update CppScripts exclusion list (#1597) 2024-05-25 01:43:32 -07:00
David Markowitz
8ae1a8ff7c fix stale reference (#1594) 2024-05-24 09:15:30 -05:00
wincent
b8d610987f Correct try-parse 2024-05-24 15:10:38 +02:00
wincent
7845131649 Test for reactive item descriptions 2024-05-24 15:06:40 +02:00
David Markowitz
f0960d48b2 Add more modular saving of config data for items (#1591)
* stubbing for saving item extra data

* add declaration to header

* modularize loading for all possible extra data

* move logic to Item

* remove extra map
2024-05-22 17:06:52 -07:00
David Markowitz
dc430d9758 Add reputation as a repeatable mission reward (#1590)
This reverts commit 7d1a28b492b263aba2008a5984dc0f5e7348a068.

Add stubbing for abbreviations

Reward reputation always if possible
2024-05-22 16:35:45 -07:00
TAHuntling
dea10c6d56 Client commands implementation (#1592)
* Adding Client Commands

Adding list of client commands provided to me by EmosewaMC

* Finished adding client commands
2024-05-22 08:32:24 -05:00
TAHuntling
ed00551982 feat: Help Command Pagination (#1581)
* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Fixed Comments

Now able to do /command help to see info for said command. Additionally this works for aliases. Fixed serialization missing from merge.

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp
2024-05-21 20:02:07 -05:00
TAHuntling
d6cac65a8d fix: Falling Off Edge in Pet Puzzle (#1584)
* FloatFix

* game activity setting

* Update dNavMesh.cpp

---------

Co-authored-by: David Markowitz <EmosewaMC@gmail.com>
2024-05-21 20:01:44 -05:00
David Markowitz
d8f079cb1b fix mpc resetting on each world load (#1588) 2024-05-20 02:43:57 -05:00
TAHuntling
c8e0bb0db0 feat: Command Sorting (#1580)
* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update dGame/dUtilities/SlashCommandHandler.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update dGame/dUtilities/SlashCommandHandler.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

* Update dGame/dUtilities/SlashCommandHandler.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Update dGame/dUtilities/SlashCommandHandler.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Update dGame/dUtilities/SlashCommandHandler.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Update dGame/dUtilities/SlashCommandHandler.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Update dGame/dUtilities/SlashCommandHandler.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Update dGame/dUtilities/SlashCommandHandler.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Update SlashCommandHandler.cpp

* Update dGame/dUtilities/SlashCommandHandler.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

---------

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
2024-05-16 22:02:30 -07:00
David Markowitz
d9d262d3f1 prevent building in a folder which contains spaces (#1583) 2024-05-16 08:05:57 -05:00
TAHuntling
d0a5678290 chore: CppScripts refactor (#1579)
* Updating CppScripts

Rewrote file to use a lambda map rather than the massive if else chain. Kept the original comments alongside each of the different scripts they were by before.

* add script tests

* Update names

* More Changes to Scripts

* Update CppScripts.cpp

* Removing Unneeded Files

* Update CppScripts.cpp

* Delete tests/dGameTests/dScriptsTests/CMakeLists.txt

* Delete tests/dGameTests/dScriptsTests/dScriptsTests.cpp

* Delete tests/dGameTests/dScriptsTests/CppScriptsOld.cpp

* Delete tests/dGameTests/dScriptsTests/CppScriptsOld.h

* Update CMakeLists.txt

* finishing up

---------

Co-authored-by: David Markowitz <EmosewaMC@gmail.com>
2024-05-16 04:50:18 -05:00
David Markowitz
35321b22d9 script fixes (#1577)
fixes an issue where the sirens would not be destroyed correctly
fixes undefined behavior in buff station

ok for real this time

actual fix for mermaids

and for general death_behavior 0 skill stuff
2024-05-16 04:30:32 -05:00
David Markowitz
8837b110ab add include guards (#1569) 2024-05-16 04:30:00 -05:00
David Markowitz
09a8c99f3e fix: mail crash from underflow and document variables (#1582)
* fix mail crash and document variables

* const
2024-05-16 04:29:48 -05:00
Terrev
e3b108e00e fv race place atm (#1570) 2024-05-13 06:18:27 -05:00
David Markowitz
9f382aca42 fix: use after free in mission progression after removing item from inventory (#1567)
that method is cursed.

no longer has ub when deleting an item from the inventory
2024-05-12 07:30:03 -05:00
David Markowitz
4d1395e522 Update CheatDetection.cpp (#1559) 2024-05-10 16:20:42 -05:00
Aaron Kimbrell
9e36510c6b chore: Bump verion to 2.3.0 (#1564)
fix versions.txt, update cmake version
2024-05-10 15:21:10 -05:00
David Markowitz
2ca61c3e57 feat: Dragonmaw (#1562)
* rigid as heck

* abstract physics creation to separate function

* loading

Update FvRacePillarDServer.cpp

consolidate abcd pillar logic

modularization

Update SimplePhysicsComponent.cpp

Update EntityManager.cpp

Update MovingPlatformComponent.cpp

still need another pass

* geiser works

* columns working finally

* consolidate logic

* constiness

* Update PhantomPhysicsComponent.cpp

* Update PhysicsComponent.cpp

* revert testing code

* add versions info

---------

Co-authored-by: Aaron Kimbre <aronwk.aaron@gmail.com>
2024-05-10 09:22:26 -05:00
Aaron Kimbrell
07cb19cc30 chore: remove json (#1561)
we can't use it currently due to threadsafety issues, so just going to remove it until we actually need it and will re-add it as a vendored file later due to cmake issues pulling in things
2024-05-02 06:35:55 -05:00
David Markowitz
794b254fe7 remove md5 new (#1560)
tested that sqlite hash is still calculated
2024-05-02 06:35:44 -05:00
Gie "Max" Vanommeslaeghe
ab7f6f0b57 Merge pull request #1558 from DarkflameUniverse/swap-update-order
fix: out of order physics updates
2024-05-01 22:33:45 +02:00
David Markowitz
58cc569c75 fix out of order physics updates
fixes an issue where physics entities were not given a chance to be marked as sleeping, causing a initial sleeping calls to be missed and causing objects that collided with one another to not register new collisions since they were sleeping at the time the new collision fired off.

Tested that Brick Fury now corectly aggros the _first_ spawn of enemies near by to him.
Tested that the turrets in crux prime now correctly shoot the _first_ wave of enemies that spawn.
2024-04-30 23:09:35 -07:00
Daniel Seiler
35c463656d Use volume for mariadb persistence (#1555)
I initially used the bind mount because it's arguably easier to back up and move around than a volume, but turns out with https://github.com/DarkflameUniverse/NexusDashboard/issues/92 it's nothing we can recommend for Docker Desktop on WSL, which unfortunately is the primary setup newcomers will try this with. So changing the default to be a volume should address that (presumably by hosting the volume within the WSL Docker VM, as opposed to the host NTFS filesystem)
2024-04-29 22:51:13 +02:00
Aaron Kimbrell
3801a97722 feat: add nlohmann/json lib (#1552)
* feat: add nlohmann/json lib

* remove build test off
2024-04-24 21:35:45 -05:00
jadebenn
0367c67c85 chore: move the pet minigame table loading logic out of petcomponent (#1551)
* move the pet minigame table loading logic out of petcomponent

* misc fixes

* actually, using paths is dumb here when they're already char strings. why bother? silly me.

* removed unga bunga reference-casting

* add back in puzzle not found error message

* pre-allocate unordered map and make getter const-correct

* Update dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

---------

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
2024-04-24 10:09:15 -05:00
jadebenn
8fdc212cda chore: Convert heap allocation to optional (#1553)
* chore: Convert heap allocation to optional

* Update dGame/dComponents/PetComponent.h

Default-initialize
2024-04-24 10:09:04 -05:00
Aaron Kimbrell
99e7349f6c feat: slashcommands for showall, findplayer, get/openhttpmoninfo, and debug world packet (#1545)
* feat: showall, findplayer, get/openhttpmoninfo

http monitor info is planned to be used later, just putting in info that i've since reverse engineered and don't want lost

Additionally add debug world packet for duture dev use

Tested all new commands and variation of command arguments

* fix missing newline at eofs

* address most feedback

* Compormise and use struct with (de)serialize

* remove httpmoninfo commands
2024-04-17 21:47:28 -05:00
jadebenn
fafe2aefad chore: Nitpicking-utils (#1549)
* nit

* GeneralUtils const-correctness and minor fixes

* use copy instead of reference for char iteration loops

* fix typo and reorganize some functions
2024-04-14 23:14:54 -07:00
David Markowitz
5049f215ba chore: Use string to access SQLite columns (#1535)
* use string to access field name

* Update DEVGMCommands.cpp

* corrected column name

* constexpr array

include <array>

Revert "constexpr array"

This reverts commit 1492e8b1773ed5fbbe767c74466ca263178ecdd4.

Revert "include <array>"

This reverts commit 2b7a67e89ad673d420f496be97f9bc51fd2d5e59.

include <array>

constexpr array

---------

Co-authored-by: jadebenn <jonahbenn@yahoo.com>
2024-04-13 23:41:51 -05:00
David Markowitz
3a6123fe36 fix console sound (#1547) 2024-04-11 10:29:49 -05:00
David Markowitz
b8b2b687e2 inherit exception for CppSQLite3Exception (#1544)
catch any exception just in case exception isnt inherited from
2024-04-10 07:32:54 -05:00
Aaron Kimbrell
d067a8d12f chore: split out slash commands into multiple files (#1539)
* chore: split out slash commands into multiple files
Breakup the monolithic file
don't register slashcommands on startup

* fix typo
2024-04-09 20:15:51 -05:00
David Markowitz
1ee45639af Update GeneralUtils.h (#1541) 2024-04-09 00:20:25 -05:00
jadebenn
db192d2cde chore: Fix use of uninitialized variable in RemoveItemFromInventory (#1540) 2024-04-08 21:50:41 -07:00
David Markowitz
28ce8ac54d remove usage of xmldoc as a ptr (#1538)
resolves a memory leak in BrickDatabase, adds stability to character save doc.

Tested that saving manually via force-save, logout and /crash all saved my position and my removed banana as expected.
The doc was always deleted on character destruction and on any updates, so this is just a semantic change (and now we no longer have new'd tinyxml2::documents on the heap)
2024-04-08 15:13:49 -05:00
David Markowitz
be0a2f6f14 fix jittering (#1537) 2024-04-08 15:13:31 -05:00
David Markowitz
3260a063cb ignore whitespace in try parse (#1536) 2024-04-08 15:13:19 -05:00
Aaron Kimbrell
feeac2e041 feat: refactor slash commands system into more scalable system (#1510)
* WIP, but working

* Scaffolding

* testing and making it compile again

* move all commands to functions

* renaming to compile

* fix failing tests

idk how these werent failing before.  Seems to have been magic.

* move commandss into their namespace
make help command useful
fix mac error

TODO: remove the multiple not founds/ rework the structure to split into help and handling

* Just need to fill out the fields, but it's all there templated

* Add all aliases, register missing commands

* All help text

* remove test logs

* improvements

pass through added code for optimizations and cleanup as well as reduce the amount of scoping for readability and maintainability

* Update SlashCommandHandler.cpp

* only save command if it is a GM command

* simplify if checks

* remove broken delimiter

* Update SlashCommandHandler.cpp

* Update SlashCommandHandler.cpp

---------

Co-authored-by: David Markowitz <EmosewaMC@gmail.com>
2024-04-08 15:11:59 -05:00
jadebenn
18c27b14c8 disable non conforming volatile behavior on MSVC (#1534) 2024-04-05 12:56:23 -05:00
jadebenn
bcfaa6c7fe const return oversight (#1532) 2024-04-05 01:14:52 -07:00
jadebenn
06e7d57e0d chore: Remove dpEntity pointers from collision checking (#1529)
* chore: Remove dpEntity pointers from collision checking

* Update fn documentation in ProximityMonitorComponent.h

* use more idiomatic method to calculate vector index

* feedback

* missed a ranges::find replacement

* adjust for feedback. last changes tonight.

* okay, also remove unneeded include. then sleep.

* for real tho

* update to use unordered_set instead of set
2024-04-05 00:52:26 -05:00
David Markowitz
b340d7c8f9 replace white and blacklist (#1530) 2024-04-05 00:51:40 -05:00
David Markowitz
24de0e5fdb Update GeneralUtils.cpp (#1528)
same check as the header
2024-04-03 19:06:29 -05:00
Aaron Kimbrell
20408d8dfe chore: remove chat_internal and processes everything over chat connection (#1508)
* WIP

* get rid of redundent case and some formatting issues

* move some things around for cleaner diffs

* remove dead code that does nothing and add connection check

* fix whitespace

* address feedback
2024-03-31 22:27:50 -05:00
David Markowitz
c1c5db6593 update4 fp check (#1524) 2024-03-31 21:46:51 -05:00
David Markowitz
884a41f36a update to current knowledge (#1523)
Should be 100% live accurate as far as logic and bitstream reads goes.

Tested with all valiant weapons and crux prime weapons (drops from dragons) that combat does not desync and that the client reports the same level and amount of skill deserialize issues as before.
2024-03-30 11:16:06 -05:00
David Markowitz
bbc0908989 Update 9_Update_Leaderboard_Storage.sql (#1520) 2024-03-30 08:18:03 -05:00
David Markowitz
5996f3cbf4 fix stewblaster stopping for non-players (#1521)
fixes an issue when stew blaster would stop for non-players and would stand still permanently due to enemy hitboxes being removed.  Tested that stewblaster only stops for players and starts moving when there are no players in the vicinity
2024-03-30 08:17:56 -05:00
jadebenn
150031861d Update README.md (#1518) 2024-03-28 21:32:46 -05:00
jadebenn
9d8e0a9c4a unbreak the stacktraces (#1516) 2024-03-27 06:10:39 +01:00
David Markowitz
bd9b790e1d feat: Add MovingAI pathing for NPCs without combatAI (#1509)
* remove goto

* Update MovementAIComponent.cpp

* convert to PathWaypoint

Easier for usage with paths

* add path parsing

* ref removal, simplification of work

* it works

* Update MovementAIComponent.cpp

* disable pathing for combat

we just need it for npcs for now, combat ai can be done later

* fixed stuttery enemies

wow

* start at ramped up speed

* add pausing and resuming

* Update MovementAIComponent.cpp

* Update MovementAIComponent.h

* Update CMakeLists.txt
2024-03-26 21:06:22 -05:00
David Markowitz
39b81b6263 rename and shorted BehaviorTemplate enum (#1512)
just a renaming of the enum and the value names and deletion of the empty cpp file.  Code compiles still.
2024-03-26 06:35:35 -05:00
David Markowitz
1e09ec92e3 Update PlayerContainer.cpp (#1513)
Prevents a bad actor from possibly spamming the server with sequential IDs and allocating a bunch of memory.

Tested that I can still send and receive friend requests
2024-03-26 06:20:45 -05:00
David Markowitz
2b253a8248 fix: movement ai remove goto, do todo, remove unused call (#1505)
* remove goto

* Update MovementAIComponent.cpp
2024-03-24 22:24:38 -05:00
David Markowitz
3262bc3a86 chore: Remove news in Behavior members (#1504)
* Remove news in behavior members

Tested that GrowingFlowers still have their SkillEvent fired with the correct parameters, gftikitorch works, sharks eating stinky fish still work

* explicitly default move assignment and copy operators/constructors

---------

Co-authored-by: jadebenn <jadebenn@users.noreply.github.com>
2024-03-24 21:43:01 -05:00
David Markowitz
3a4e554da9 update switch behavior (#1503)
was using very old code from pre-foss that has not been updated with the new behavior knowledge.  The code has been updated accordingly to what the client expects.

Tested that ice shurikens can now destroy the legs of the skeleton towers in crux prime.  Tested that the following weapons can still do damage to enemies and objects in the world:
surikens of ice
serratorizer
Super Morning Star
Super Dagger
elite long barrel blaster (charge and normal)
Mosaic Wand
2024-03-24 14:01:12 -05:00
jadebenn
35ce8771e5 chore: supress warnings on external library headers and actually get rid of the last old-style casts (#1502)
* chore: supress warnings on external library headers and actually get rid of the last old-style casts

* remove commented out section I forgot

* update cmake required version to 3.25 unless we can find another way to do this

* update readme

* Update CMakeLists.txt
2024-03-17 20:48:09 -05:00
David Markowitz
b9092a3cce update serialization, remove unused variable (#1501)
Tested that players show up as normal on each others screens, tested that money magnet still works with item 8600, tested that gravity still works in Moon Base.
2024-03-10 01:15:43 -06:00
David Markowitz
0b4f70a76b Update StoryBoxInteractServer.cpp (#1500)
Update StoryBoxInteractServer.cpp
2024-03-08 19:29:40 -06:00
David Markowitz
4bc4624bc9 feat: add further MovementAI skeleton (#1499)
* add movement ai skeleton

Zone loading code is tested to load and read the correct values using logs.  other ldf data is unaffected as I walked around crux and dragons/apes can still spawn and be killed.

* format
2024-03-08 19:29:01 -06:00
295 changed files with 37984 additions and 5508 deletions

3
.gitmodules vendored
View File

@@ -17,6 +17,3 @@
[submodule "thirdparty/magic_enum"]
path = thirdparty/magic_enum
url = https://github.com/Neargye/magic_enum.git
[submodule "thirdparty/fmt"]
path = thirdparty/fmt
url = https://github.com/fmtlib/fmt.git

View File

@@ -1,11 +1,18 @@
cmake_minimum_required(VERSION 3.18)
cmake_minimum_required(VERSION 3.25)
project(Darkflame)
# check if the path to the source directory contains a space
if("${CMAKE_SOURCE_DIR}" MATCHES " ")
message(FATAL_ERROR "The server cannot build in the path (" ${CMAKE_SOURCE_DIR} ") because it contains a space. Please move the server to a path without spaces.")
endif()
include(CTest)
set(CMAKE_CXX_STANDARD 20)
set(CXX_STANDARD_REQUIRED ON)
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects
set(CMAKE_CXX_VISIBILITY_PRESET hidden) # Set C++ symbol visibility to default to hidden
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export the compile commands for debugging
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C and C++ symbol visibility to hide inlined functions
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# Read variables from file
@@ -72,7 +79,8 @@ if(UNIX)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -O2 -fPIC")
elseif(MSVC)
# Skip warning for invalid conversion from size_t to uint32_t for all targets below for now
add_compile_options("/wd4267" "/utf-8")
# Also disable non-portable MSVC volatile behavior
add_compile_options("/wd4267" "/utf-8" "/volatile:iso")
elseif(WIN32)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif()
@@ -102,7 +110,7 @@ make_directory(${CMAKE_BINARY_DIR}/resServer)
make_directory(${CMAKE_BINARY_DIR}/logs)
# Copy resource files on first build
set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blacklist.dcf")
set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blocklist.dcf")
message(STATUS "Checking resource file integrity")
include(Utils)
@@ -213,29 +221,29 @@ if (APPLE)
endif()
# Load all of our third party directories
add_subdirectory(thirdparty)
add_subdirectory(thirdparty SYSTEM)
# Create our list of include directories
set(INCLUDED_DIRECTORIES
include_directories(
"dPhysics"
"dNavigation"
"dNet"
"thirdparty/magic_enum/include/magic_enum"
"thirdparty/raknet/Source"
"thirdparty/tinyxml2"
"thirdparty/recastnavigation"
"thirdparty/SQLite"
"thirdparty/cpplinq"
"thirdparty/cpp-httplib"
"thirdparty/MD5"
"tests"
"tests/dCommonTests"
"tests/dGameTests"
"tests/dGameTests/dComponentsTests"
SYSTEM "thirdparty/magic_enum/include/magic_enum"
SYSTEM "thirdparty/raknet/Source"
SYSTEM "thirdparty/tinyxml2"
SYSTEM "thirdparty/recastnavigation"
SYSTEM "thirdparty/SQLite"
SYSTEM "thirdparty/cpplinq"
SYSTEM "thirdparty/cpp-httplib"
SYSTEM "thirdparty/MD5"
)
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
@@ -244,14 +252,9 @@ if(APPLE)
include_directories("/usr/local/include/")
endif()
# Actually include the directories from our list
foreach(dir ${INCLUDED_DIRECTORIES})
include_directories(${PROJECT_SOURCE_DIR}/${dir})
endforeach()
# Add linking directories:
if (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast -Werror") # Warning flags
endif()
file(
GLOB HEADERS_DZONEMANAGER
@@ -287,7 +290,7 @@ add_subdirectory(dPhysics)
add_subdirectory(dServer)
# Create a list of common libraries shared between all binaries
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "fmt" "raknet" "MariaDB::ConnCpp" "magic_enum")
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "MariaDB::ConnCpp" "magic_enum")
# Add platform specific common libraries
if(UNIX)

View File

@@ -1,6 +1,6 @@
PROJECT_VERSION_MAJOR=1
PROJECT_VERSION_MINOR=1
PROJECT_VERSION_PATCH=1
PROJECT_VERSION_MAJOR=2
PROJECT_VERSION_MINOR=3
PROJECT_VERSION_PATCH=0
# Debugging
# Set DYNAMIC to 1 to enable the -rdynamic flag for the linker, yielding some symbols in crashlogs.

View File

@@ -31,7 +31,7 @@ COPY --from=build /app/build/*Server /app/
# Necessary suplimentary files
COPY --from=build /app/build/*.ini /app/configs/
COPY --from=build /app/build/vanity/*.* /app/vanity/*
COPY --from=build /app/build/vanity/*.* /app/vanity/
COPY --from=build /app/build/navmeshes /app/navmeshes
COPY --from=build /app/build/migrations /app/migrations
COPY --from=build /app/build/*.dcf /app/
@@ -39,7 +39,7 @@ COPY --from=build /app/build/*.dcf /app/
# backup of config and vanity files to copy to the host incase
# of a mount clobbering the copy from above
COPY --from=build /app/build/*.ini /app/default-configs/
COPY --from=build /app/build/vanity/*.* /app/default-vanity/*
COPY --from=build /app/build/vanity/*.* /app/default-vanity/
# needed as the container runs with the root user
# and therefore sudo doesn't exist

View File

@@ -51,7 +51,7 @@ git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer
### Windows packages
Ensure that you have either the [MSVC C++ compiler](https://visualstudio.microsoft.com/vs/features/cplusplus/) (recommended) or the [Clang compiler](https://github.com/llvm/llvm-project/releases/) installed.
You'll also need to download and install [CMake](https://cmake.org/download/) (version <font size="4">**CMake version 3.18**</font> or later!).
You'll also need to download and install [CMake](https://cmake.org/download/) (version <font size="4">**CMake version 3.25**</font> or later!).
### MacOS packages
Ensure you have [brew](https://brew.sh) installed.
@@ -73,7 +73,7 @@ sudo apt install build-essential gcc zlib1g-dev libssl-dev openssl mariadb-serve
```
#### Required CMake version
This project uses <font size="4">**CMake version 3.18**</font> or higher and as such you will need to ensure you have this version installed.
This project uses <font size="4">**CMake version 3.25**</font> or higher and as such you will need to ensure you have this version installed.
You can check your CMake version by using the following command in a terminal.
```bash
cmake --version

View File

@@ -141,6 +141,6 @@ elseif(APPLE)
endif()
# Add directories to include lists
target_include_directories(MariaDB::ConnCpp INTERFACE ${MARIADB_INCLUDE_DIR})
target_include_directories(MariaDB::ConnCpp SYSTEM INTERFACE ${MARIADB_INCLUDE_DIR})
set(MariaDB_FOUND TRUE)

View File

@@ -54,14 +54,14 @@ int main(int argc, char** argv) {
Server::SetupLogger("AuthServer");
if (!Game::logger) return EXIT_FAILURE;
Log::Info("Starting Auth server...");
Log::Info("Version: {:s}", PROJECT_VERSION);
Log::Info("Compiled on: {:s}", __TIMESTAMP__);
LOG("Starting Auth server...");
LOG("Version: %s", PROJECT_VERSION);
LOG("Compiled on: %s", __TIMESTAMP__);
try {
Database::Connect();
} catch (sql::SQLException& ex) {
Log::Info("Got an error while connecting to the database: {:s}", ex.what());
LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("AuthServer");
delete Game::server;
delete Game::logger;
@@ -77,7 +77,7 @@ int main(int argc, char** argv) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
}
Log::Info("Master is at {:s}:{:d}", masterIP, masterPort);
LOG("Master is at %s:%d", masterIP.c_str(), masterPort);
Game::randomEngine = std::mt19937(time(0));
@@ -110,7 +110,7 @@ int main(int argc, char** argv) {
framesSinceMasterDisconnect++;
if (framesSinceMasterDisconnect >= authFramerate) {
Log::Info("No connection to master!");
LOG("No connection to master!");
break; //Exit our loop, shut down.
}
} else framesSinceMasterDisconnect = 0;
@@ -151,7 +151,7 @@ int main(int argc, char** argv) {
std::this_thread::sleep_until(t);
}
Log::Info("Exited Main Loop! (signal {:d})", Game::lastSignal);
LOG("Exited Main Loop! (signal %d)", Game::lastSignal);
//Delete our objects here:
Database::Destroy("AuthServer");
delete Game::server;

View File

@@ -1,6 +1,6 @@
add_executable(AuthServer "AuthServer.cpp")
target_link_libraries(AuthServer PRIVATE ${COMMON_LIBRARIES} dServer)
target_link_libraries(AuthServer ${COMMON_LIBRARIES} dServer)
target_include_directories(AuthServer PRIVATE ${PROJECT_SOURCE_DIR}/dServer)

View File

@@ -27,8 +27,8 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) {
ExportWordlistToDCF(filepath + ".dcf", true);
}
if (BinaryIO::DoesFileExist("blacklist.dcf")) {
ReadWordlistDCF("blacklist.dcf", false);
if (BinaryIO::DoesFileExist("blocklist.dcf")) {
ReadWordlistDCF("blocklist.dcf", false);
}
//Read player names that are ok as well:
@@ -44,20 +44,20 @@ dChatFilter::~dChatFilter() {
m_DeniedWords.clear();
}
void dChatFilter::ReadWordlistPlaintext(const std::string& filepath, bool whiteList) {
void dChatFilter::ReadWordlistPlaintext(const std::string& filepath, bool allowList) {
std::ifstream file(filepath);
if (file) {
std::string line;
while (std::getline(file, line)) {
line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
std::transform(line.begin(), line.end(), line.begin(), ::tolower); //Transform to lowercase
if (whiteList) m_ApprovedWords.push_back(CalculateHash(line));
if (allowList) m_ApprovedWords.push_back(CalculateHash(line));
else m_DeniedWords.push_back(CalculateHash(line));
}
}
}
bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) {
bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool allowList) {
std::ifstream file(filepath, std::ios::binary);
if (file) {
fileHeader hdr;
@@ -70,13 +70,13 @@ bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) {
if (hdr.formatVersion == formatVersion) {
size_t wordsToRead = 0;
BinaryIO::BinaryRead(file, wordsToRead);
if (whiteList) m_ApprovedWords.reserve(wordsToRead);
if (allowList) m_ApprovedWords.reserve(wordsToRead);
else m_DeniedWords.reserve(wordsToRead);
size_t word = 0;
for (size_t i = 0; i < wordsToRead; ++i) {
BinaryIO::BinaryRead(file, word);
if (whiteList) m_ApprovedWords.push_back(word);
if (allowList) m_ApprovedWords.push_back(word);
else m_DeniedWords.push_back(word);
}
@@ -90,14 +90,14 @@ bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) {
return false;
}
void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool whiteList) {
void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool allowList) {
std::ofstream file(filepath, std::ios::binary | std::ios_base::out);
if (file) {
BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::header));
BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::formatVersion));
BinaryIO::BinaryWrite(file, size_t(whiteList ? m_ApprovedWords.size() : m_DeniedWords.size()));
BinaryIO::BinaryWrite(file, size_t(allowList ? m_ApprovedWords.size() : m_DeniedWords.size()));
for (size_t word : whiteList ? m_ApprovedWords : m_DeniedWords) {
for (size_t word : allowList ? m_ApprovedWords : m_DeniedWords) {
BinaryIO::BinaryWrite(file, word);
}
@@ -105,10 +105,10 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool whiteLis
}
}
std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool whiteList) {
std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) {
if (gmLevel > eGameMasterLevel::FORUM_MODERATOR) return { }; //If anything but a forum mod, return true.
if (message.empty()) return { };
if (!whiteList && m_DeniedWords.empty()) return { { 0, message.length() } };
if (!allowList && m_DeniedWords.empty()) return { { 0, message.length() } };
std::stringstream sMessage(message);
std::string segment;
@@ -126,16 +126,16 @@ std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::
size_t hash = CalculateHash(segment);
if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && whiteList) {
if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && allowList) {
listOfBadSegments.emplace_back(position, originalSegment.length());
}
if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && whiteList) {
if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && allowList) {
m_UserUnapprovedWordCache.push_back(hash);
listOfBadSegments.emplace_back(position, originalSegment.length());
}
if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !whiteList) {
if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !allowList) {
m_UserUnapprovedWordCache.push_back(hash);
listOfBadSegments.emplace_back(position, originalSegment.length());
}

View File

@@ -21,10 +21,10 @@ public:
dChatFilter(const std::string& filepath, bool dontGenerateDCF);
~dChatFilter();
void ReadWordlistPlaintext(const std::string& filepath, bool whiteList);
bool ReadWordlistDCF(const std::string& filepath, bool whiteList);
void ExportWordlistToDCF(const std::string& filepath, bool whiteList);
std::vector<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool whiteList = true);
void ReadWordlistPlaintext(const std::string& filepath, bool allowList);
bool ReadWordlistDCF(const std::string& filepath, bool allowList);
void ExportWordlistToDCF(const std::string& filepath, bool allowList);
std::vector<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true);
private:
bool m_DontGenerateDCF;

View File

@@ -11,6 +11,6 @@ add_compile_definitions(ChatServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}
add_library(dChatServer ${DCHATSERVER_SOURCES})
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer")
target_link_libraries(dChatServer PRIVATE ${COMMON_LIBRARIES} dChatFilter)
target_link_libraries(ChatServer PRIVATE ${COMMON_LIBRARIES} dChatFilter dChatServer dServer)
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter)
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer)

View File

@@ -1,6 +1,6 @@
#include "ChatIgnoreList.h"
#include "PlayerContainer.h"
#include "eChatInternalMessageType.h"
#include "eChatMessageType.h"
#include "BitStreamUtils.h"
#include "Game.h"
#include "Logger.h"
@@ -13,7 +13,7 @@
// The only thing not auto-handled is instance activities force joining the team on the server.
void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const ChatIgnoreList::Response type) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receivingPlayer);
//portion that will get routed:
@@ -27,16 +27,16 @@ void ChatIgnoreList::GetIgnoreList(Packet* packet) {
auto& receiver = Game::playerContainer.GetPlayerDataMutable(playerId);
if (!receiver) {
Log::Info("Tried to get ignore list, but player {:d} not found in container", playerId);
LOG("Tried to get ignore list, but player %llu not found in container", playerId);
return;
}
if (!receiver.ignoredPlayers.empty()) {
Log::Debug("Player {:d} already has an ignore list, but is requesting it again.", playerId);
LOG_DEBUG("Player %llu already has an ignore list, but is requesting it again.", playerId);
} else {
auto ignoreList = Database::Get()->GetIgnoreList(static_cast<uint32_t>(playerId));
if (ignoreList.empty()) {
Log::Debug("Player {:d} has no ignores", playerId);
LOG_DEBUG("Player %llu has no ignores", playerId);
return;
}
@@ -69,13 +69,13 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
auto& receiver = Game::playerContainer.GetPlayerDataMutable(playerId);
if (!receiver) {
Log::Info("Tried to get ignore list, but player {:d} not found in container", playerId);
LOG("Tried to get ignore list, but player %llu not found in container", playerId);
return;
}
constexpr int32_t MAX_IGNORES = 32;
if (receiver.ignoredPlayers.size() > MAX_IGNORES) {
Log::Debug("Player {:d} has too many ignores", playerId);
LOG_DEBUG("Player %llu has too many ignores", playerId);
return;
}
@@ -91,11 +91,11 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
// Check if the player exists
LWOOBJID ignoredPlayerId = LWOOBJID_EMPTY;
if (toIgnoreStr == receiver.playerName || toIgnoreStr.find("[GM]") == 0) {
Log::Debug("Player {:d} tried to ignore themselves", playerId);
LOG_DEBUG("Player %llu tried to ignore themselves", playerId);
bitStream.Write(ChatIgnoreList::AddResponse::GENERAL_ERROR);
} else if (std::count(receiver.ignoredPlayers.begin(), receiver.ignoredPlayers.end(), toIgnoreStr) > 0) {
Log::Debug("Player {:d} is already ignoring {:s}", playerId, toIgnoreStr);
LOG_DEBUG("Player %llu is already ignoring %s", playerId, toIgnoreStr.c_str());
bitStream.Write(ChatIgnoreList::AddResponse::ALREADY_IGNORED);
} else {
@@ -105,7 +105,7 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
// Fall back to query
auto player = Database::Get()->GetCharacterInfo(toIgnoreStr);
if (!player || player->name != toIgnoreStr) {
Log::Debug("Player {:s} not found", toIgnoreStr);
LOG_DEBUG("Player %s not found", toIgnoreStr.c_str());
} else {
ignoredPlayerId = player->id;
}
@@ -119,7 +119,7 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
GeneralUtils::SetBit(ignoredPlayerId, eObjectBits::PERSISTENT);
receiver.ignoredPlayers.emplace_back(toIgnoreStr, ignoredPlayerId);
Log::Debug("Player {:d} is ignoring {:s}", playerId, toIgnoreStr);
LOG_DEBUG("Player %llu is ignoring %s", playerId, toIgnoreStr.c_str());
bitStream.Write(ChatIgnoreList::AddResponse::SUCCESS);
} else {
@@ -141,7 +141,7 @@ void ChatIgnoreList::RemoveIgnore(Packet* packet) {
auto& receiver = Game::playerContainer.GetPlayerDataMutable(playerId);
if (!receiver) {
Log::Info("Tried to get ignore list, but player {:d} not found in container", playerId);
LOG("Tried to get ignore list, but player %llu not found in container", playerId);
return;
}
@@ -153,7 +153,7 @@ void ChatIgnoreList::RemoveIgnore(Packet* packet) {
auto toRemove = std::remove(receiver.ignoredPlayers.begin(), receiver.ignoredPlayers.end(), removedIgnoreStr);
if (toRemove == receiver.ignoredPlayers.end()) {
Log::Debug("Player {:d} is not ignoring {:s}", playerId, removedIgnoreStr);
LOG_DEBUG("Player %llu is not ignoring %s", playerId, removedIgnoreStr.c_str());
return;
}

View File

@@ -14,11 +14,11 @@
#include "eObjectBits.h"
#include "eConnectionType.h"
#include "eChatMessageType.h"
#include "eChatInternalMessageType.h"
#include "eClientMessageType.h"
#include "eGameMessageType.h"
#include "StringifiedEnum.h"
#include "eGameMasterLevel.h"
#include "ChatPackets.h"
void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Get from the packet which player we want to do something with:
@@ -60,7 +60,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Now, we need to send the friendlist to the server they came from:
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(playerID);
//portion that will get routed:
@@ -93,7 +93,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
auto& requestor = Game::playerContainer.GetPlayerDataMutable(requestorPlayerID);
if (!requestor) {
Log::Info("No requestor player {:d} sent to {:s} found.", requestorPlayerID, playerName);
LOG("No requestor player %llu sent to %s found.", requestorPlayerID, playerName.c_str());
return;
}
@@ -355,6 +355,67 @@ void ChatPacketHandler::HandleGMLevelUpdate(Packet* packet) {
inStream.Read(player.gmLevel);
}
void ChatPacketHandler::HandleWho(Packet* packet) {
CINSTREAM_SKIP_HEADER;
FindPlayerRequest request;
request.Deserialize(inStream);
const auto& sender = Game::playerContainer.GetPlayerData(request.requestor);
if (!sender) return;
const auto& player = Game::playerContainer.GetPlayerData(request.playerName.GetAsString());
bool online = player;
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(request.requestor);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::WHO_RESPONSE);
bitStream.Write<uint8_t>(online);
bitStream.Write(player.zoneID.GetMapID());
bitStream.Write(player.zoneID.GetInstanceID());
bitStream.Write(player.zoneID.GetCloneID());
bitStream.Write(request.playerName);
SystemAddress sysAddr = sender.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::HandleShowAll(Packet* packet) {
CINSTREAM_SKIP_HEADER;
ShowAllRequest request;
request.Deserialize(inStream);
const auto& sender = Game::playerContainer.GetPlayerData(request.requestor);
if (!sender) return;
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(request.requestor);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::SHOW_ALL_RESPONSE);
bitStream.Write<uint8_t>(!request.displayZoneData && !request.displayIndividualPlayers);
bitStream.Write(Game::playerContainer.GetPlayerCount());
bitStream.Write(Game::playerContainer.GetSimCount());
bitStream.Write<uint8_t>(request.displayIndividualPlayers);
bitStream.Write<uint8_t>(request.displayZoneData);
if (request.displayZoneData || request.displayIndividualPlayers){
for (auto& [playerID, playerData ]: Game::playerContainer.GetAllPlayers()){
if (!playerData) continue;
bitStream.Write<uint8_t>(0); // structure packing
if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName));
if (request.displayZoneData) {
bitStream.Write(playerData.zoneID.GetMapID());
bitStream.Write(playerData.zoneID.GetInstanceID());
bitStream.Write(playerData.zoneID.GetCloneID());
}
}
}
SystemAddress sysAddr = sender.sysAddr;
SEND_PACKET;
}
// the structure the client uses to send this packet is shared in many chat messages
// that are sent to the server. Because of this, there are large gaps of unused data in chat messages
void ChatPacketHandler::HandleChatMessage(Packet* packet) {
@@ -376,7 +437,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
LUWString message(size);
inStream.Read(message);
Log::Info("Got a message from ({:s}) via [{:s}]: {:s}", sender.playerName, StringifiedEnum::ToString(channel), message.GetAsString());
LOG("Got a message from (%s) via [%s]: %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str());
switch (channel) {
case eChatChannel::TEAM: {
@@ -391,7 +452,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
break;
}
default:
Log::Info("Unhandled Chat channel [{:s}]", StringifiedEnum::ToString(channel));
LOG("Unhandled Chat channel [%s]", StringifiedEnum::ToString(channel).data());
break;
}
}
@@ -412,7 +473,7 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
inStream.IgnoreBytes(4);
inStream.Read(channel);
if (channel != eChatChannel::PRIVATE_CHAT) Log::Info("WARNING: Received Private chat with the wrong channel!");
if (channel != eChatChannel::PRIVATE_CHAT) LOG("WARNING: Received Private chat with the wrong channel!");
inStream.Read(size);
inStream.IgnoreBytes(77);
@@ -424,7 +485,7 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
LUWString message(size);
inStream.Read(message);
Log::Info("Got a message from ({:s}) via [{:s}]: {:s} to {:s}", sender.playerName, StringifiedEnum::ToString(channel), message.GetAsString(), receiverName);
LOG("Got a message from (%s) via [%s]: %s to %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str(), receiverName.c_str());
const auto& receiver = Game::playerContainer.GetPlayerData(receiverName);
if (!receiver) {
@@ -454,7 +515,7 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(routeTo.playerID);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::PRIVATE_CHAT_MESSAGE);
@@ -506,13 +567,13 @@ void ChatPacketHandler::HandleTeamInvite(Packet* packet) {
if (team->memberIDs.size() > 3) {
// no more teams greater than 4
Log::Info("Someone tried to invite a 5th player to a team");
LOG("Someone tried to invite a 5th player to a team");
return;
}
SendTeamInvite(other, player);
Log::Info("Got team invite: {:d} -> {:s}", playerID, invitedPlayer.GetAsString());
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
}
void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
@@ -526,7 +587,7 @@ void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
LWOOBJID leaderID = LWOOBJID_EMPTY;
inStream.Read(leaderID);
Log::Info("Accepted invite: {:d} -> {:d} ({:d})", playerID, leaderID, declined);
LOG("Accepted invite: %llu -> %llu (%d)", playerID, leaderID, declined);
if (declined) {
return;
@@ -535,13 +596,13 @@ void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
auto* team = Game::playerContainer.GetTeam(leaderID);
if (team == nullptr) {
Log::Info("Failed to find team for leader ({:d})", leaderID);
LOG("Failed to find team for leader (%llu)", leaderID);
team = Game::playerContainer.GetTeam(playerID);
}
if (team == nullptr) {
Log::Info("Failed to find team for player ({:d})", playerID);
LOG("Failed to find team for player (%llu)", playerID);
return;
}
@@ -557,7 +618,7 @@ void ChatPacketHandler::HandleTeamLeave(Packet* packet) {
auto* team = Game::playerContainer.GetTeam(playerID);
Log::Info("({:d}) leaving team", playerID);
LOG("(%llu) leaving team", playerID);
if (team != nullptr) {
Game::playerContainer.RemoveMember(team, playerID, false, false, true);
@@ -575,7 +636,7 @@ void ChatPacketHandler::HandleTeamKick(Packet* packet) {
inStream.Read(kickedPlayer);
Log::Info("({:d}) kicking ({:s}) from team", playerID, kickedPlayer.GetAsString());
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str());
const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString());
@@ -608,7 +669,7 @@ void ChatPacketHandler::HandleTeamPromote(Packet* packet) {
inStream.IgnoreBytes(4);
inStream.Read(promotedPlayer);
Log::Info("({:d}) promoting ({:s}) to team leader", playerID, promotedPlayer.GetAsString());
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str());
const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString());
@@ -696,7 +757,7 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -711,7 +772,7 @@ void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerD
void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -738,7 +799,7 @@ void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool b
void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -763,7 +824,7 @@ void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64L
void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -780,7 +841,7 @@ void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i
void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -809,7 +870,7 @@ void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFr
void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -835,7 +896,7 @@ void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bD
void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -869,7 +930,7 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
[bool] - is FTP*/
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(friendData.playerID);
//portion that will get routed:
@@ -906,7 +967,7 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
}
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -920,7 +981,7 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready, uint8_t isBestFriendRequest) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
// Portion that will get routed:
@@ -943,7 +1004,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:

View File

@@ -50,6 +50,8 @@ namespace ChatPacketHandler {
void HandleFriendResponse(Packet* packet);
void HandleRemoveFriend(Packet* packet);
void HandleGMLevelUpdate(Packet* packet);
void HandleWho(Packet* packet);
void HandleShowAll(Packet* packet);
void HandleChatMessage(Packet* packet);
void HandlePrivateChatMessage(Packet* packet);

View File

@@ -17,7 +17,6 @@
#include "PlayerContainer.h"
#include "ChatPacketHandler.h"
#include "eChatMessageType.h"
#include "eChatInternalMessageType.h"
#include "eWorldMessageType.h"
#include "ChatIgnoreList.h"
#include "StringifiedEnum.h"
@@ -60,9 +59,9 @@ int main(int argc, char** argv) {
//Read our config:
Log::Info("Starting Chat server...");
Log::Info("Version: {:s}", PROJECT_VERSION);
Log::Info("Compiled on: {:s}", __TIMESTAMP__);
LOG("Starting Chat server...");
LOG("Version: %s", PROJECT_VERSION);
LOG("Compiled on: %s", __TIMESTAMP__);
try {
std::string clientPathStr = Game::config->GetValue("client_location");
@@ -74,7 +73,7 @@ int main(int argc, char** argv) {
Game::assetManager = new AssetManager(clientPath);
} catch (std::runtime_error& ex) {
Log::Info("Got an error while setting up assets: {:s}", ex.what());
LOG("Got an error while setting up assets: %s", ex.what());
return EXIT_FAILURE;
}
@@ -83,7 +82,7 @@ int main(int argc, char** argv) {
try {
Database::Connect();
} catch (sql::SQLException& ex) {
Log::Info("Got an error while connecting to the database: {:s}", ex.what());
LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("ChatServer");
delete Game::server;
delete Game::logger;
@@ -181,48 +180,30 @@ int main(int argc, char** argv) {
void HandlePacket(Packet* packet) {
if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) {
Log::Info("A server has disconnected, erasing their connected players from the list.");
}
LOG("A server has disconnected, erasing their connected players from the list.");
} else if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) {
LOG("A server is connecting, awaiting user list.");
} else if (packet->length < 4 || packet->data[0] != ID_USER_PACKET_ENUM) return; // Nothing left to process or not the right packet type
if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) {
Log::Info("A server is connecting, awaiting user list.");
}
CINSTREAM;
inStream.SetReadOffset(BYTES_TO_BITS(1));
if (packet->length < 4) return; // Nothing left to process. Need 4 bytes to continue.
eConnectionType connection;
eChatMessageType chatMessageID;
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::CHAT_INTERNAL) {
switch (static_cast<eChatInternalMessageType>(packet->data[3])) {
case eChatInternalMessageType::PLAYER_ADDED_NOTIFICATION:
Game::playerContainer.InsertPlayer(packet);
break;
case eChatInternalMessageType::PLAYER_REMOVED_NOTIFICATION:
Game::playerContainer.RemovePlayer(packet);
break;
case eChatInternalMessageType::MUTE_UPDATE:
inStream.Read(connection);
if (connection != eConnectionType::CHAT) return;
inStream.Read(chatMessageID);
switch (chatMessageID) {
case eChatMessageType::GM_MUTE:
Game::playerContainer.MuteUpdate(packet);
break;
case eChatInternalMessageType::CREATE_TEAM:
case eChatMessageType::CREATE_TEAM:
Game::playerContainer.CreateTeamServer(packet);
break;
case eChatInternalMessageType::ANNOUNCEMENT: {
//we just forward this packet to every connected server
CINSTREAM;
Game::server->Send(inStream, packet->systemAddress, true); //send to everyone except origin
break;
}
default:
Log::Info("Unknown CHAT_INTERNAL id: {:d}", static_cast<int>(packet->data[3]));
}
}
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::CHAT) {
eChatMessageType chat_message_type = static_cast<eChatMessageType>(packet->data[3]);
switch (chat_message_type) {
case eChatMessageType::GET_FRIENDS_LIST:
ChatPacketHandler::HandleFriendlistRequest(packet);
break;
@@ -296,6 +277,23 @@ void HandlePacket(Packet* packet) {
ChatPacketHandler::HandleGMLevelUpdate(packet);
break;
case eChatMessageType::LOGIN_SESSION_NOTIFY:
Game::playerContainer.InsertPlayer(packet);
break;
case eChatMessageType::GM_ANNOUNCE:{
// we just forward this packet to every connected server
inStream.ResetReadPointer();
Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin
}
break;
case eChatMessageType::UNEXPECTED_DISCONNECT:
Game::playerContainer.RemovePlayer(packet);
break;
case eChatMessageType::WHO:
ChatPacketHandler::HandleWho(packet);
break;
case eChatMessageType::SHOW_ALL:
ChatPacketHandler::HandleShowAll(packet);
break;
case eChatMessageType::USER_CHANNEL_CHAT_MESSAGE:
case eChatMessageType::WORLD_DISCONNECT_REQUEST:
case eChatMessageType::WORLD_PROXIMITY_RESPONSE:
@@ -308,7 +306,6 @@ void HandlePacket(Packet* packet) {
case eChatMessageType::GUILD_KICK:
case eChatMessageType::GUILD_GET_STATUS:
case eChatMessageType::GUILD_GET_ALL:
case eChatMessageType::SHOW_ALL:
case eChatMessageType::BLUEPRINT_MODERATED:
case eChatMessageType::BLUEPRINT_MODEL_READY:
case eChatMessageType::PROPERTY_READY_FOR_APPROVAL:
@@ -323,7 +320,6 @@ void HandlePacket(Packet* packet) {
case eChatMessageType::CSR_REQUEST:
case eChatMessageType::CSR_REPLY:
case eChatMessageType::GM_KICK:
case eChatMessageType::GM_ANNOUNCE:
case eChatMessageType::WORLD_ROUTE_PACKET:
case eChatMessageType::GET_ZONE_POPULATIONS:
case eChatMessageType::REQUEST_MINIMUM_CHAT_MODE:
@@ -332,33 +328,18 @@ void HandlePacket(Packet* packet) {
case eChatMessageType::UGCMANIFEST_REPORT_DONE_FILE:
case eChatMessageType::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
case eChatMessageType::UGCC_REQUEST:
case eChatMessageType::WHO:
case eChatMessageType::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
case eChatMessageType::ACHIEVEMENT_NOTIFY:
case eChatMessageType::GM_CLOSE_PRIVATE_CHAT_WINDOW:
case eChatMessageType::UNEXPECTED_DISCONNECT:
case eChatMessageType::PLAYER_READY:
case eChatMessageType::GET_DONATION_TOTAL:
case eChatMessageType::UPDATE_DONATION:
case eChatMessageType::PRG_CSR_COMMAND:
case eChatMessageType::HEARTBEAT_REQUEST_FROM_WORLD:
case eChatMessageType::UPDATE_FREE_TRIAL_STATUS:
Log::Info("Unhandled CHAT Message id: {:s} {:d}", StringifiedEnum::ToString(chat_message_type), GeneralUtils::ToUnderlying(chat_message_type));
LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID);
break;
default:
Log::Info("Unknown CHAT Message id: {:d}", GeneralUtils::ToUnderlying(chat_message_type));
}
}
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::WORLD) {
switch (static_cast<eWorldMessageType>(packet->data[3])) {
case eWorldMessageType::ROUTE_PACKET: {
Log::Info("Routing packet from world");
break;
}
default:
Log::Info("Unknown World id: {:d}", static_cast<int>(packet->data[3]));
}
LOG("Unknown CHAT Message id: %i", chatMessageID);
}
}

View File

@@ -9,9 +9,9 @@
#include "BitStreamUtils.h"
#include "Database.h"
#include "eConnectionType.h"
#include "eChatInternalMessageType.h"
#include "ChatPackets.h"
#include "dConfig.h"
#include "eChatMessageType.h"
void PlayerContainer::Initialize() {
m_MaxNumberOfBestFriends =
@@ -28,7 +28,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerId;
if (!inStream.Read(playerId)) {
Log::Warn("Failed to read player ID");
LOG("Failed to read player ID");
return;
}
@@ -49,8 +49,9 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
data.sysAddr = packet->systemAddress;
m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName);
m_PlayerCount++;
Log::Info("Added user: {:s} ({:d}), zone: {:d}", data.playerName, data.playerID, data.zoneID.GetMapID());
LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID());
Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID());
}
@@ -64,7 +65,7 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
const auto& player = GetPlayerData(playerID);
if (!player) {
Log::Info("Failed to find user: {:d}", playerID);
LOG("Failed to find user: %llu", playerID);
return;
}
@@ -87,7 +88,8 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
}
}
Log::Info("Removed user: {:d}", playerID);
m_PlayerCount--;
LOG("Removed user: %llu", playerID);
m_Players.erase(playerID);
Database::Get()->UpdateActivityLog(playerID, eActivityType::PlayerLoggedOut, player.zoneID.GetMapID());
@@ -103,7 +105,7 @@ void PlayerContainer::MuteUpdate(Packet* packet) {
auto& player = this->GetPlayerDataMutable(playerID);
if (!player) {
Log::Warn("Failed to find user: {:d}", playerID);
LOG("Failed to find user: %llu", playerID);
return;
}
@@ -145,7 +147,7 @@ void PlayerContainer::CreateTeamServer(Packet* packet) {
void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::MUTE_UPDATE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE);
bitStream.Write(player);
bitStream.Write(time);
@@ -207,7 +209,7 @@ TeamData* PlayerContainer::GetTeam(LWOOBJID playerID) {
void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
if (team->memberIDs.size() >= 4) {
Log::Warn("Tried to add player to team that already had 4 players");
LOG("Tried to add player to team that already had 4 players");
const auto& player = GetPlayerData(playerID);
if (!player) return;
ChatPackets::SendSystemMessage(player.sysAddr, u"The teams is full! You have not been added to a team!");
@@ -352,7 +354,7 @@ void PlayerContainer::TeamStatusUpdate(TeamData* team) {
void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::TEAM_UPDATE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::TEAM_GET_STATUS);
bitStream.Write(team->teamID);
bitStream.Write(deleteTeam);
@@ -390,7 +392,7 @@ LWOOBJID PlayerContainer::GetId(const std::u16string& playerName) {
}
PlayerData& PlayerContainer::GetPlayerDataMutable(const LWOOBJID& playerID) {
return m_Players[playerID];
return m_Players.contains(playerID) ? m_Players[playerID] : m_Players[LWOOBJID_EMPTY];
}
PlayerData& PlayerContainer::GetPlayerDataMutable(const std::string& playerName) {

View File

@@ -71,6 +71,9 @@ public:
const PlayerData& GetPlayerData(const std::string& playerName);
PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID);
PlayerData& GetPlayerDataMutable(const std::string& playerName);
uint32_t GetPlayerCount() { return m_PlayerCount; };
uint32_t GetSimCount() { return m_SimCount; };
const std::map<LWOOBJID, PlayerData>& GetAllPlayers() { return m_Players; };
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
TeamData* CreateTeam(LWOOBJID leader, bool local = false);
@@ -93,5 +96,7 @@ private:
std::unordered_map<LWOOBJID, std::u16string> m_Names;
uint32_t m_MaxNumberOfBestFriends = 5;
uint32_t m_MaxNumberOfFriends = 50;
uint32_t m_PlayerCount = 0;
uint32_t m_SimCount = 0;
};

View File

@@ -29,7 +29,7 @@ void RakNet::BitStream::Write<AMFBaseValue&>(AMFBaseValue& value) {
break;
}
default: {
Log::Warn("Encountered unwritable AMFType {:d}!", GeneralUtils::ToUnderlying(type));
LOG("Encountered unwritable AMFType %i!", type);
}
case eAmf::Undefined:
case eAmf::Null:

View File

@@ -54,14 +54,14 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
completeUncompressedModel.append(reinterpret_cast<char*>(uncompressedChunk.get()));
completeUncompressedModel.resize(previousSize + actualUncompressedSize);
} else {
Log::Warn("Failed to inflate chunk {} for model %llu. Error: {}", chunkCount, model.id, err);
LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err);
break;
}
chunkCount++;
}
std::unique_ptr<tinyxml2::XMLDocument> document = std::make_unique<tinyxml2::XMLDocument>();
if (!document) {
Log::Warn("Failed to initialize tinyxml document. Aborting.");
LOG("Failed to initialize tinyxml document. Aborting.");
return 0;
}
@@ -70,13 +70,13 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
"</LXFML>",
completeUncompressedModel.length() >= 15 ? completeUncompressedModel.length() - 15 : 0) == std::string::npos
) {
Log::Info("Brick-by-brick model {} will be deleted!", model.id);
LOG("Brick-by-brick model %llu will be deleted!", model.id);
Database::Get()->DeleteUgcModelData(model.id);
modelsTruncated++;
}
}
} else {
Log::Info("Brick-by-brick model {} will be deleted!", model.id);
LOG("Brick-by-brick model %llu will be deleted!", model.id);
Database::Get()->DeleteUgcModelData(model.id);
modelsTruncated++;
}
@@ -121,11 +121,11 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
try {
Database::Get()->UpdateUgcModelData(model.id, outputStringStream);
Log::Info("Updated model {} to sd0", model.id);
LOG("Updated model %i to sd0", model.id);
updatedModels++;
} catch (sql::SQLException exception) {
Log::Warn("Failed to update model {}. This model should be inspected manually to see why."
"The database error is {}", model.id, exception.what());
LOG("Failed to update model %i. This model should be inspected manually to see why."
"The database error is %s", model.id, exception.what());
}
}
}

View File

@@ -70,6 +70,5 @@ else ()
endif ()
target_link_libraries(dCommon
PUBLIC fmt
PRIVATE ZLIB::ZLIB bcrypt tinyxml2
INTERFACE dDatabase)

View File

@@ -28,7 +28,7 @@ void make_minidump(EXCEPTION_POINTERS* e) {
"_%4d%02d%02d_%02d%02d%02d.dmp",
t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond);
}
Log::Info("Creating crash dump {:s}", name);
LOG("Creating crash dump %s", name);
auto hFile = CreateFileA(name, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE)
return;
@@ -83,7 +83,7 @@ struct bt_ctx {
static inline void Bt(struct backtrace_state* state) {
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
Log::Info("backtrace is enabled, crash dump located at {:s}", fileName);
LOG("backtrace is enabled, crash dump located at %s", fileName.c_str());
FILE* file = fopen(fileName.c_str(), "w+");
if (file != nullptr) {
backtrace_print(state, 2, file);
@@ -95,13 +95,13 @@ static inline void Bt(struct backtrace_state* state) {
static void ErrorCallback(void* data, const char* msg, int errnum) {
auto* ctx = (struct bt_ctx*)data;
fmt::print(stderr, "ERROR: {:s} ({:d})", msg, errnum);
fprintf(stderr, "ERROR: %s (%d)", msg, errnum);
ctx->error = 1;
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
FILE* file = fopen(fileName.c_str(), "w+");
if (file != nullptr) {
fmt::print(file, "ERROR: {:s} ({:d})", msg, errnum);
fprintf(file, "ERROR: %s (%d)", msg, errnum);
fclose(file);
}
}
@@ -119,13 +119,15 @@ void CatchUnhandled(int sig) {
try {
if (eptr) std::rethrow_exception(eptr);
} catch(const std::exception& e) {
Log::Warn("Caught exception: '{:s}'", e.what());
LOG("Caught exception: '%s'", e.what());
} catch (...) {
LOG("Caught unknown exception.");
}
#ifndef INCLUDE_BACKTRACE
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
Log::Warn("Encountered signal {:d}, creating crash dump {:s}", sig, fileName);
LOG("Encountered signal %i, creating crash dump %s", sig, fileName.c_str());
if (Diagnostics::GetProduceMemoryDump()) {
GenerateDump();
}
@@ -143,7 +145,7 @@ void CatchUnhandled(int sig) {
FILE* file = fopen(fileName.c_str(), "w+");
if (file != NULL) {
fmt::println(file, "Error: signal {:d}:", sig);
fprintf(file, "Error: signal %d:\n", sig);
}
// Print the stack trace
for (size_t i = 0; i < size; i++) {
@@ -165,9 +167,9 @@ void CatchUnhandled(int sig) {
}
}
Log::Info("[{:02d}] {:s}", i, functionName);
LOG("[%02zu] %s", i, functionName.c_str());
if (file != NULL) {
fmt::println(file, "[{:02d}] {:s}", i, functionName);
fprintf(file, "[%02zu] %s\n", i, functionName.c_str());
}
}
# else // defined(__GNUG__)
@@ -208,7 +210,7 @@ void MakeBacktrace() {
sigaction(SIGFPE, &sigact, nullptr) != 0 ||
sigaction(SIGABRT, &sigact, nullptr) != 0 ||
sigaction(SIGILL, &sigact, nullptr) != 0) {
fmt::println(stderr, "error setting signal handler for {:d} ({:s})",
fprintf(stderr, "error setting signal handler for %d (%s)\n",
SIGSEGV,
strsignal(SIGSEGV));

View File

@@ -42,7 +42,7 @@ bool FdbToSqlite::Convert::ConvertDatabase(AssetStream& buffer) {
CDClientDatabase::ExecuteQuery("COMMIT;");
} catch (CppSQLite3Exception& e) {
Log::Warn("Encountered error {:s} converting FDB to SQLite", e.errorMessage());
LOG("Encountered error %s converting FDB to SQLite", e.errorMessage());
return false;
}

View File

@@ -8,23 +8,23 @@
#include <map>
template <typename T>
inline size_t MinSize(size_t size, const std::basic_string_view<T>& string) {
if (size == size_t(-1) || size > string.size()) {
static inline size_t MinSize(const size_t size, const std::basic_string_view<T> string) {
if (size == SIZE_MAX || size > string.size()) {
return string.size();
} else {
return size;
}
}
inline bool IsLeadSurrogate(char16_t c) {
inline bool IsLeadSurrogate(const char16_t c) {
return (0xD800 <= c) && (c <= 0xDBFF);
}
inline bool IsTrailSurrogate(char16_t c) {
inline bool IsTrailSurrogate(const char16_t c) {
return (0xDC00 <= c) && (c <= 0xDFFF);
}
inline void PushUTF8CodePoint(std::string& ret, char32_t cp) {
inline void PushUTF8CodePoint(std::string& ret, const char32_t cp) {
if (cp <= 0x007F) {
ret.push_back(static_cast<uint8_t>(cp));
} else if (cp <= 0x07FF) {
@@ -46,16 +46,16 @@ inline void PushUTF8CodePoint(std::string& ret, char32_t cp) {
constexpr const char16_t REPLACEMENT_CHARACTER = 0xFFFD;
bool _IsSuffixChar(uint8_t c) {
bool static _IsSuffixChar(const uint8_t c) {
return (c & 0xC0) == 0x80;
}
bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
size_t rem = slice.length();
bool GeneralUtils::details::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
const size_t rem = slice.length();
if (slice.empty()) return false;
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&slice.front());
if (rem > 0) {
uint8_t first = bytes[0];
const uint8_t first = bytes[0];
if (first < 0x80) { // 1 byte character
out = static_cast<uint32_t>(first & 0x7F);
slice.remove_prefix(1);
@@ -64,7 +64,7 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
// middle byte, not valid at start, fall through
} else if (first < 0xE0) { // two byte character
if (rem > 1) {
uint8_t second = bytes[1];
const uint8_t second = bytes[1];
if (_IsSuffixChar(second)) {
out = (static_cast<uint32_t>(first & 0x1F) << 6)
+ static_cast<uint32_t>(second & 0x3F);
@@ -74,8 +74,8 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
}
} else if (first < 0xF0) { // three byte character
if (rem > 2) {
uint8_t second = bytes[1];
uint8_t third = bytes[2];
const uint8_t second = bytes[1];
const uint8_t third = bytes[2];
if (_IsSuffixChar(second) && _IsSuffixChar(third)) {
out = (static_cast<uint32_t>(first & 0x0F) << 12)
+ (static_cast<uint32_t>(second & 0x3F) << 6)
@@ -86,9 +86,9 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
}
} else if (first < 0xF8) { // four byte character
if (rem > 3) {
uint8_t second = bytes[1];
uint8_t third = bytes[2];
uint8_t fourth = bytes[3];
const uint8_t second = bytes[1];
const uint8_t third = bytes[2];
const uint8_t fourth = bytes[3];
if (_IsSuffixChar(second) && _IsSuffixChar(third) && _IsSuffixChar(fourth)) {
out = (static_cast<uint32_t>(first & 0x07) << 18)
+ (static_cast<uint32_t>(second & 0x3F) << 12)
@@ -107,7 +107,7 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
}
/// See <https://www.ietf.org/rfc/rfc2781.html#section-2.1>
bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) {
bool PushUTF16CodePoint(std::u16string& output, const uint32_t U, const size_t size) {
if (output.length() >= size) return false;
if (U < 0x10000) {
// If U < 0x10000, encode U as a 16-bit unsigned integer and terminate.
@@ -120,7 +120,7 @@ bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) {
// Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
// U' must be less than or equal to 0xFFFFF. That is, U' can be
// represented in 20 bits.
uint32_t Ut = U - 0x10000;
const uint32_t Ut = U - 0x10000;
// Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
// 0xDC00, respectively. These integers each have 10 bits free to
@@ -141,25 +141,25 @@ bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) {
} else return false;
}
std::u16string GeneralUtils::UTF8ToUTF16(const std::string_view& string, size_t size) {
size_t newSize = MinSize(size, string);
std::u16string GeneralUtils::UTF8ToUTF16(const std::string_view string, const size_t size) {
const size_t newSize = MinSize(size, string);
std::u16string output;
output.reserve(newSize);
std::string_view iterator = string;
uint32_t c;
while (_NextUTF8Char(iterator, c) && PushUTF16CodePoint(output, c, size)) {}
while (details::_NextUTF8Char(iterator, c) && PushUTF16CodePoint(output, c, size)) {}
return output;
}
//! Converts an std::string (ASCII) to UCS-2 / UTF-16
std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view& string, size_t size) {
size_t newSize = MinSize(size, string);
std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view string, const size_t size) {
const size_t newSize = MinSize(size, string);
std::u16string ret;
ret.reserve(newSize);
for (size_t i = 0; i < newSize; i++) {
char c = string[i];
for (size_t i = 0; i < newSize; ++i) {
const char c = string[i];
// Note: both 7-bit ascii characters and REPLACEMENT_CHARACTER fit in one char16_t
ret.push_back((c > 0 && c <= 127) ? static_cast<char16_t>(c) : REPLACEMENT_CHARACTER);
}
@@ -169,18 +169,18 @@ std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view& string, size_t
//! Converts a (potentially-ill-formed) UTF-16 string to UTF-8
//! See: <http://simonsapin.github.io/wtf-8/#decoding-ill-formed-utf-16>
std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view& string, size_t size) {
size_t newSize = MinSize(size, string);
std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const size_t size) {
const size_t newSize = MinSize(size, string);
std::string ret;
ret.reserve(newSize);
for (size_t i = 0; i < newSize; i++) {
char16_t u = string[i];
for (size_t i = 0; i < newSize; ++i) {
const char16_t u = string[i];
if (IsLeadSurrogate(u) && (i + 1) < newSize) {
char16_t next = string[i + 1];
const char16_t next = string[i + 1];
if (IsTrailSurrogate(next)) {
i += 1;
char32_t cp = 0x10000
const char32_t cp = 0x10000
+ ((static_cast<char32_t>(u) - 0xD800) << 10)
+ (static_cast<char32_t>(next) - 0xDC00);
PushUTF8CodePoint(ret, cp);
@@ -195,40 +195,40 @@ std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view& string, size_t
return ret;
}
bool GeneralUtils::CaseInsensitiveStringCompare(const std::string& a, const std::string& b) {
bool GeneralUtils::CaseInsensitiveStringCompare(const std::string_view a, const std::string_view b) {
return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); });
}
// MARK: Bits
//! Sets a specific bit in a signed 64-bit integer
int64_t GeneralUtils::SetBit(int64_t value, uint32_t index) {
int64_t GeneralUtils::SetBit(int64_t value, const uint32_t index) {
return value |= 1ULL << index;
}
//! Clears a specific bit in a signed 64-bit integer
int64_t GeneralUtils::ClearBit(int64_t value, uint32_t index) {
int64_t GeneralUtils::ClearBit(int64_t value, const uint32_t index) {
return value &= ~(1ULL << index);
}
//! Checks a specific bit in a signed 64-bit integer
bool GeneralUtils::CheckBit(int64_t value, uint32_t index) {
bool GeneralUtils::CheckBit(int64_t value, const uint32_t index) {
return value & (1ULL << index);
}
bool GeneralUtils::ReplaceInString(std::string& str, const std::string& from, const std::string& to) {
size_t start_pos = str.find(from);
bool GeneralUtils::ReplaceInString(std::string& str, const std::string_view from, const std::string_view to) {
const size_t start_pos = str.find(from);
if (start_pos == std::string::npos)
return false;
str.replace(start_pos, from.length(), to);
return true;
}
std::vector<std::wstring> GeneralUtils::SplitString(std::wstring& str, wchar_t delimiter) {
std::vector<std::wstring> GeneralUtils::SplitString(const std::wstring_view str, const wchar_t delimiter) {
std::vector<std::wstring> vector = std::vector<std::wstring>();
std::wstring current;
for (const auto& c : str) {
for (const wchar_t c : str) {
if (c == delimiter) {
vector.push_back(current);
current = L"";
@@ -237,15 +237,15 @@ std::vector<std::wstring> GeneralUtils::SplitString(std::wstring& str, wchar_t d
}
}
vector.push_back(current);
vector.push_back(std::move(current));
return vector;
}
std::vector<std::u16string> GeneralUtils::SplitString(const std::u16string& str, char16_t delimiter) {
std::vector<std::u16string> GeneralUtils::SplitString(const std::u16string_view str, const char16_t delimiter) {
std::vector<std::u16string> vector = std::vector<std::u16string>();
std::u16string current;
for (const auto& c : str) {
for (const char16_t c : str) {
if (c == delimiter) {
vector.push_back(current);
current = u"";
@@ -254,17 +254,15 @@ std::vector<std::u16string> GeneralUtils::SplitString(const std::u16string& str,
}
}
vector.push_back(current);
vector.push_back(std::move(current));
return vector;
}
std::vector<std::string> GeneralUtils::SplitString(const std::string& str, char delimiter) {
std::vector<std::string> GeneralUtils::SplitString(const std::string_view str, const char delimiter) {
std::vector<std::string> vector = std::vector<std::string>();
std::string current = "";
for (size_t i = 0; i < str.length(); i++) {
char c = str[i];
for (const char c : str) {
if (c == delimiter) {
vector.push_back(current);
current = "";
@@ -273,8 +271,7 @@ std::vector<std::string> GeneralUtils::SplitString(const std::string& str, char
}
}
vector.push_back(current);
vector.push_back(std::move(current));
return vector;
}
@@ -283,7 +280,7 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream& inStream) {
inStream.Read<uint32_t>(length);
std::u16string string;
for (auto i = 0; i < length; i++) {
for (uint32_t i = 0; i < length; ++i) {
uint16_t c;
inStream.Read(c);
string.push_back(c);
@@ -292,35 +289,35 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream& inStream) {
return string;
}
std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string& folder) {
std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string_view folder) {
// Because we dont know how large the initial number before the first _ is we need to make it a map like so.
std::map<uint32_t, std::string> filenames{};
for (auto& t : std::filesystem::directory_iterator(folder)) {
auto filename = t.path().filename().string();
auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
filenames.insert(std::make_pair(index, filename));
std::map<uint32_t, std::string> filenames{};
for (const auto& t : std::filesystem::directory_iterator(folder)) {
auto filename = t.path().filename().string();
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
filenames.emplace(index, std::move(filename));
}
// Now sort the map by the oldest migration.
std::vector<std::string> sortedFiles{};
auto fileIterator = filenames.begin();
std::map<uint32_t, std::string>::iterator oldest = filenames.begin();
auto fileIterator = filenames.cbegin();
auto oldest = filenames.cbegin();
while (!filenames.empty()) {
if (fileIterator == filenames.end()) {
if (fileIterator == filenames.cend()) {
sortedFiles.push_back(oldest->second);
filenames.erase(oldest);
fileIterator = filenames.begin();
oldest = filenames.begin();
fileIterator = filenames.cbegin();
oldest = filenames.cbegin();
continue;
}
if (oldest->first > fileIterator->first) oldest = fileIterator;
fileIterator++;
++fileIterator;
}
return sortedFiles;
}
#ifdef DARKFLAME_PLATFORM_MACOS
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
// MacOS floating-point parse function specializations
namespace GeneralUtils::details {

View File

@@ -3,17 +3,18 @@
// C++
#include <charconv>
#include <cstdint>
#include <random>
#include <ctime>
#include <functional>
#include <optional>
#include <random>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <optional>
#include <functional>
#include <type_traits>
#include <stdexcept>
#include "BitStream.h"
#include "NiPoint3.h"
#include "dPlatforms.h"
#include "Game.h"
#include "Logger.h"
@@ -32,29 +33,31 @@ namespace GeneralUtils {
//! Converts a plain ASCII string to a UTF-16 string
/*!
\param string The string to convert
\param size A size to trim the string to. Default is -1 (No trimming)
\param size A size to trim the string to. Default is SIZE_MAX (No trimming)
\return An UTF-16 representation of the string
*/
std::u16string ASCIIToUTF16(const std::string_view& string, size_t size = -1);
std::u16string ASCIIToUTF16(const std::string_view string, const size_t size = SIZE_MAX);
//! Converts a UTF-8 String to a UTF-16 string
/*!
\param string The string to convert
\param size A size to trim the string to. Default is -1 (No trimming)
\param size A size to trim the string to. Default is SIZE_MAX (No trimming)
\return An UTF-16 representation of the string
*/
std::u16string UTF8ToUTF16(const std::string_view& string, size_t size = -1);
std::u16string UTF8ToUTF16(const std::string_view string, const size_t size = SIZE_MAX);
//! Internal, do not use
bool _NextUTF8Char(std::string_view& slice, uint32_t& out);
namespace details {
//! Internal, do not use
bool _NextUTF8Char(std::string_view& slice, uint32_t& out);
}
//! Converts a UTF-16 string to a UTF-8 string
/*!
\param string The string to convert
\param size A size to trim the string to. Default is -1 (No trimming)
\param size A size to trim the string to. Default is SIZE_MAX (No trimming)
\return An UTF-8 representation of the string
*/
std::string UTF16ToWTF8(const std::u16string_view& string, size_t size = -1);
std::string UTF16ToWTF8(const std::u16string_view string, const size_t size = SIZE_MAX);
/**
* Compares two basic strings but does so ignoring case sensitivity
@@ -62,7 +65,7 @@ namespace GeneralUtils {
* \param b the second string to compare against the first string
* @return if the two strings are equal
*/
bool CaseInsensitiveStringCompare(const std::string& a, const std::string& b);
bool CaseInsensitiveStringCompare(const std::string_view a, const std::string_view b);
// MARK: Bits
@@ -70,9 +73,9 @@ namespace GeneralUtils {
//! Sets a bit on a numerical value
template <typename T>
inline void SetBit(T& value, eObjectBits bits) {
inline void SetBit(T& value, const eObjectBits bits) {
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
auto index = static_cast<size_t>(bits);
const auto index = static_cast<size_t>(bits);
if (index > (sizeof(T) * 8) - 1) {
return;
}
@@ -82,9 +85,9 @@ namespace GeneralUtils {
//! Clears a bit on a numerical value
template <typename T>
inline void ClearBit(T& value, eObjectBits bits) {
inline void ClearBit(T& value, const eObjectBits bits) {
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
auto index = static_cast<size_t>(bits);
const auto index = static_cast<size_t>(bits);
if (index > (sizeof(T) * 8 - 1)) {
return;
}
@@ -97,14 +100,14 @@ namespace GeneralUtils {
\param value The value to set the bit for
\param index The index of the bit
*/
int64_t SetBit(int64_t value, uint32_t index);
int64_t SetBit(int64_t value, const uint32_t index);
//! Clears a specific bit in a signed 64-bit integer
/*!
\param value The value to clear the bit from
\param index The index of the bit
*/
int64_t ClearBit(int64_t value, uint32_t index);
int64_t ClearBit(int64_t value, const uint32_t index);
//! Checks a specific bit in a signed 64-bit integer
/*!
@@ -112,19 +115,19 @@ namespace GeneralUtils {
\param index The index of the bit
\return Whether or not the bit is set
*/
bool CheckBit(int64_t value, uint32_t index);
bool CheckBit(int64_t value, const uint32_t index);
bool ReplaceInString(std::string& str, const std::string& from, const std::string& to);
bool ReplaceInString(std::string& str, const std::string_view from, const std::string_view to);
std::u16string ReadWString(RakNet::BitStream& inStream);
std::vector<std::wstring> SplitString(std::wstring& str, wchar_t delimiter);
std::vector<std::wstring> SplitString(const std::wstring_view str, const wchar_t delimiter);
std::vector<std::u16string> SplitString(const std::u16string& str, char16_t delimiter);
std::vector<std::u16string> SplitString(const std::u16string_view str, const char16_t delimiter);
std::vector<std::string> SplitString(const std::string& str, char delimiter);
std::vector<std::string> SplitString(const std::string_view str, const char delimiter);
std::vector<std::string> GetSqlFileNamesFromFolder(const std::string& folder);
std::vector<std::string> GetSqlFileNamesFromFolder(const std::string_view folder);
// Concept constraining to enum types
template <typename T>
@@ -144,7 +147,7 @@ namespace GeneralUtils {
// If a boolean, present an alias to an intermediate integral type for parsing
template <Numeric T> requires std::same_as<T, bool>
struct numeric_parse<T> { using type = uint32_t; };
struct numeric_parse<T> { using type = uint8_t; };
// Shorthand type alias
template <Numeric T>
@@ -156,8 +159,9 @@ namespace GeneralUtils {
* @returns An std::optional containing the desired value if it is equivalent to the string
*/
template <Numeric T>
[[nodiscard]] std::optional<T> TryParse(const std::string_view str) {
[[nodiscard]] std::optional<T> TryParse(std::string_view str) {
numeric_parse_t<T> result;
while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1);
const char* const strEnd = str.data() + str.size();
const auto [parseEnd, ec] = std::from_chars(str.data(), strEnd, result);
@@ -166,7 +170,7 @@ namespace GeneralUtils {
return isParsed ? static_cast<T>(result) : std::optional<T>{};
}
#ifdef DARKFLAME_PLATFORM_MACOS
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
// MacOS floating-point parse helper function specializations
namespace details {
@@ -181,8 +185,10 @@ namespace GeneralUtils {
* @returns An std::optional containing the desired value if it is equivalent to the string
*/
template <std::floating_point T>
[[nodiscard]] std::optional<T> TryParse(const std::string_view str) noexcept
[[nodiscard]] std::optional<T> TryParse(std::string_view str) noexcept
try {
while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1);
size_t parseNum;
const T result = details::_parse<T>(str, parseNum);
const bool isParsed = str.length() == parseNum;
@@ -202,7 +208,7 @@ namespace GeneralUtils {
* @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters
*/
template <typename T>
[[nodiscard]] std::optional<NiPoint3> TryParse(const std::string& strX, const std::string& strY, const std::string& strZ) {
[[nodiscard]] std::optional<NiPoint3> TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ) {
const auto x = TryParse<float>(strX);
if (!x) return std::nullopt;
@@ -214,17 +220,17 @@ namespace GeneralUtils {
}
/**
* The TryParse overload for handling NiPoint3 by passingn a reference to a vector of three strings
* @param str The string vector representing the X, Y, and Xcoordinates
* The TryParse overload for handling NiPoint3 by passing a span of three strings
* @param str The string vector representing the X, Y, and Z coordinates
* @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters
*/
template <typename T>
[[nodiscard]] std::optional<NiPoint3> TryParse(const std::vector<std::string>& str) {
[[nodiscard]] std::optional<NiPoint3> TryParse(const std::span<const std::string> str) {
return (str.size() == 3) ? TryParse<NiPoint3>(str[0], str[1], str[2]) : std::nullopt;
}
template <typename T>
std::u16string to_u16string(T value) {
std::u16string to_u16string(const T value) {
return GeneralUtils::ASCIIToUTF16(std::to_string(value));
}
@@ -243,7 +249,7 @@ namespace GeneralUtils {
\param max The maximum to generate to
*/
template <typename T>
inline T GenerateRandomNumber(std::size_t min, std::size_t max) {
inline T GenerateRandomNumber(const std::size_t min, const std::size_t max) {
// Make sure it is a numeric type
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
@@ -270,10 +276,10 @@ namespace GeneralUtils {
// on Windows we need to undef these or else they conflict with our numeric limits calls
// DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS
#ifdef _WIN32
#undef min
#undef max
#endif
#ifdef _WIN32
#undef min
#undef max
#endif
template <typename T>
inline T GenerateRandomNumber() {

92
dCommon/Implementation.h Normal file
View File

@@ -0,0 +1,92 @@
#ifndef __IMPLEMENTATION_H__
#define __IMPLEMENTATION_H__
#include <functional>
#include <optional>
/**
* @brief A way to defer the implementation of an action.
*
* @tparam R The result of the action.
* @tparam T The types of the arguments that the implementation requires.
*/
template <typename R, typename... T>
class Implementation {
public:
typedef std::function<std::optional<R>(T...)> ImplementationFunction;
/**
* @brief Sets the implementation of the action.
*
* @param implementation The implementation of the action.
*/
void SetImplementation(const ImplementationFunction& implementation) {
this->implementation = implementation;
}
/**
* @brief Clears the implementation of the action.
*/
void ClearImplementation() {
implementation.reset();
}
/**
* @brief Checks if the implementation is set.
*
* @return true If the implementation is set.
* @return false If the implementation is not set.
*/
bool IsSet() const {
return implementation.has_value();
}
/**
* @brief Executes the implementation if it is set.
*
* @param args The arguments to pass to the implementation.
* @return std::optional<R> The optional result of the implementation. If the result is not set, it indicates that the default action should be taken.
*/
std::optional<R> Execute(T... args) const {
return IsSet() ? implementation.value()(args...) : std::nullopt;
}
/**
* @brief Exectues the implementation if it is set, otherwise returns a default value.
*
* @param args The arguments to pass to the implementation.
* @param defaultValue The default value to return if the implementation is not set or should not be deferred.
*/
R ExecuteWithDefault(T... args, const R& defaultValue) const {
return Execute(args...).value_or(defaultValue);
}
/**
* = operator overload.
*/
Implementation& operator=(const Implementation& other) {
implementation = other.implementation;
return *this;
}
/**
* = operator overload.
*/
Implementation& operator=(const ImplementationFunction& implementation) {
this->implementation = implementation;
return *this;
}
/**
* () operator overload.
*/
std::optional<R> operator()(T... args) {
return !IsSet() ? std::nullopt : implementation(args...);
}
private:
std::optional<ImplementationFunction> implementation;
};
#endif //!__IMPLEMENTATION_H__

View File

@@ -48,7 +48,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
try {
type = static_cast<eLDFType>(strtol(ldfTypeAndValue.first.data(), &storage, 10));
} catch (std::exception) {
Log::Warn("Attempted to process invalid ldf type ({:s}) from string ({:s})", ldfTypeAndValue.first, format);
LOG("Attempted to process invalid ldf type (%s) from string (%s)", ldfTypeAndValue.first.data(), format.data());
return nullptr;
}
@@ -63,7 +63,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
case LDF_TYPE_S32: {
const auto data = GeneralUtils::TryParse<int32_t>(ldfTypeAndValue.second);
if (!data) {
Log::Warn("Attempted to process invalid int32 value ({:s}) from string ({:s})", ldfTypeAndValue.second, format);
LOG("Warning: Attempted to process invalid int32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<int32_t>(key, data.value());
@@ -74,7 +74,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
case LDF_TYPE_FLOAT: {
const auto data = GeneralUtils::TryParse<float>(ldfTypeAndValue.second);
if (!data) {
Log::Warn("Attempted to process invalid float value ({:s}) from string ({:s})", ldfTypeAndValue.second, format);
LOG("Warning: Attempted to process invalid float value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<float>(key, data.value());
@@ -84,7 +84,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
case LDF_TYPE_DOUBLE: {
const auto data = GeneralUtils::TryParse<double>(ldfTypeAndValue.second);
if (!data) {
Log::Warn("Attempted to process invalid double value ({:s}) from string ({:s})", ldfTypeAndValue.second, format);
LOG("Warning: Attempted to process invalid double value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<double>(key, data.value());
@@ -102,7 +102,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
} else {
const auto dataOptional = GeneralUtils::TryParse<uint32_t>(ldfTypeAndValue.second);
if (!dataOptional) {
Log::Warn("Attempted to process invalid uint32 value ({:s}) from string ({:s})", ldfTypeAndValue.second, format);
LOG("Warning: Attempted to process invalid uint32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
data = dataOptional.value();
@@ -122,7 +122,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
} else {
const auto dataOptional = GeneralUtils::TryParse<bool>(ldfTypeAndValue.second);
if (!dataOptional) {
Log::Warn("Attempted to process invalid bool value ({:s}) from string ({:s})", ldfTypeAndValue.second, format);
LOG("Warning: Attempted to process invalid bool value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
data = dataOptional.value();
@@ -135,7 +135,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
case LDF_TYPE_U64: {
const auto data = GeneralUtils::TryParse<uint64_t>(ldfTypeAndValue.second);
if (!data) {
Log::Warn("Attempted to process invalid uint64 value ({:s}) from string ({:s})", ldfTypeAndValue.second, format);
LOG("Warning: Attempted to process invalid uint64 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<uint64_t>(key, data.value());
@@ -145,7 +145,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
case LDF_TYPE_OBJID: {
const auto data = GeneralUtils::TryParse<LWOOBJID>(ldfTypeAndValue.second);
if (!data) {
Log::Warn("Attempted to process invalid LWOOBJID value ({:s}) from string ({:s})", ldfTypeAndValue.second, format);
LOG("Warning: Attempted to process invalid LWOOBJID value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<LWOOBJID>(key, data.value());
@@ -159,12 +159,12 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
}
case LDF_TYPE_UNKNOWN: {
Log::Warn("Attempted to process invalid unknown value ({:s}) from string ({:s})", ldfTypeAndValue.second, format);
LOG("Warning: Attempted to process invalid unknown value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
break;
}
default: {
Log::Warn("Attempted to process invalid LDF type ({:d}) from string ({:s})", GeneralUtils::ToUnderlying(type), format);
LOG("Warning: Attempted to process invalid LDF type (%d) from string (%s)", type, format.data());
break;
}
}

View File

@@ -31,22 +31,22 @@ public:
virtual ~LDFBaseData() {}
virtual void WriteToPacket(RakNet::BitStream& packet) = 0;
virtual void WriteToPacket(RakNet::BitStream& packet) const = 0;
virtual const std::u16string& GetKey() = 0;
virtual const std::u16string& GetKey() const = 0;
virtual eLDFType GetValueType() = 0;
virtual eLDFType GetValueType() const = 0;
/** Gets a string from the key/value pair
* @param includeKey Whether or not to include the key in the data
* @param includeTypeId Whether or not to include the type id in the data
* @return The string representation of the data
*/
virtual std::string GetString(bool includeKey = true, bool includeTypeId = true) = 0;
virtual std::string GetString(bool includeKey = true, bool includeTypeId = true) const = 0;
virtual std::string GetValueAsString() = 0;
virtual std::string GetValueAsString() const = 0;
virtual LDFBaseData* Copy() = 0;
virtual LDFBaseData* Copy() const = 0;
/**
* Given an input string, return the data as a LDF key.
@@ -62,7 +62,7 @@ private:
T value;
//! Writes the key to the packet
void WriteKey(RakNet::BitStream& packet) {
void WriteKey(RakNet::BitStream& packet) const {
packet.Write<uint8_t>(this->key.length() * sizeof(uint16_t));
for (uint32_t i = 0; i < this->key.length(); ++i) {
packet.Write<uint16_t>(this->key[i]);
@@ -70,7 +70,7 @@ private:
}
//! Writes the value to the packet
void WriteValue(RakNet::BitStream& packet) {
void WriteValue(RakNet::BitStream& packet) const {
packet.Write<uint8_t>(this->GetValueType());
packet.Write(this->value);
}
@@ -90,7 +90,7 @@ public:
/*!
\return The value
*/
const T& GetValue(void) { return this->value; }
const T& GetValue(void) const { return this->value; }
//! Sets the value
/*!
@@ -102,13 +102,13 @@ public:
/*!
\return The value string
*/
std::string GetValueString(void) { return ""; }
std::string GetValueString(void) const { return ""; }
//! Writes the data to a packet
/*!
\param packet The packet
*/
void WriteToPacket(RakNet::BitStream& packet) override {
void WriteToPacket(RakNet::BitStream& packet) const override {
this->WriteKey(packet);
this->WriteValue(packet);
}
@@ -117,13 +117,13 @@ public:
/*!
\return The key
*/
const std::u16string& GetKey(void) override { return this->key; }
const std::u16string& GetKey(void) const override { return this->key; }
//! Gets the LDF Type
/*!
\return The LDF value type
*/
eLDFType GetValueType(void) override { return LDF_TYPE_UNKNOWN; }
eLDFType GetValueType(void) const override { return LDF_TYPE_UNKNOWN; }
//! Gets the string data
/*!
@@ -131,7 +131,7 @@ public:
\param includeTypeId Whether or not to include the type id in the data
\return The string representation of the data
*/
std::string GetString(const bool includeKey = true, const bool includeTypeId = true) override {
std::string GetString(const bool includeKey = true, const bool includeTypeId = true) const override {
if (GetValueType() == -1) {
return GeneralUtils::UTF16ToWTF8(this->key) + "=-1:<server variable>";
}
@@ -154,11 +154,11 @@ public:
return stream.str();
}
std::string GetValueAsString() override {
std::string GetValueAsString() const override {
return this->GetValueString();
}
LDFBaseData* Copy() override {
LDFBaseData* Copy() const override {
return new LDFData<T>(key, value);
}
@@ -166,19 +166,19 @@ public:
};
// LDF Types
template<> inline eLDFType LDFData<std::u16string>::GetValueType(void) { return LDF_TYPE_UTF_16; };
template<> inline eLDFType LDFData<int32_t>::GetValueType(void) { return LDF_TYPE_S32; };
template<> inline eLDFType LDFData<float>::GetValueType(void) { return LDF_TYPE_FLOAT; };
template<> inline eLDFType LDFData<double>::GetValueType(void) { return LDF_TYPE_DOUBLE; };
template<> inline eLDFType LDFData<uint32_t>::GetValueType(void) { return LDF_TYPE_U32; };
template<> inline eLDFType LDFData<bool>::GetValueType(void) { return LDF_TYPE_BOOLEAN; };
template<> inline eLDFType LDFData<uint64_t>::GetValueType(void) { return LDF_TYPE_U64; };
template<> inline eLDFType LDFData<LWOOBJID>::GetValueType(void) { return LDF_TYPE_OBJID; };
template<> inline eLDFType LDFData<std::string>::GetValueType(void) { return LDF_TYPE_UTF_8; };
template<> inline eLDFType LDFData<std::u16string>::GetValueType(void) const { return LDF_TYPE_UTF_16; };
template<> inline eLDFType LDFData<int32_t>::GetValueType(void) const { return LDF_TYPE_S32; };
template<> inline eLDFType LDFData<float>::GetValueType(void) const { return LDF_TYPE_FLOAT; };
template<> inline eLDFType LDFData<double>::GetValueType(void) const { return LDF_TYPE_DOUBLE; };
template<> inline eLDFType LDFData<uint32_t>::GetValueType(void) const { return LDF_TYPE_U32; };
template<> inline eLDFType LDFData<bool>::GetValueType(void) const { return LDF_TYPE_BOOLEAN; };
template<> inline eLDFType LDFData<uint64_t>::GetValueType(void) const { return LDF_TYPE_U64; };
template<> inline eLDFType LDFData<LWOOBJID>::GetValueType(void) const { return LDF_TYPE_OBJID; };
template<> inline eLDFType LDFData<std::string>::GetValueType(void) const { return LDF_TYPE_UTF_8; };
// The specialized version for std::u16string (UTF-16)
template<>
inline void LDFData<std::u16string>::WriteValue(RakNet::BitStream& packet) {
inline void LDFData<std::u16string>::WriteValue(RakNet::BitStream& packet) const {
packet.Write<uint8_t>(this->GetValueType());
packet.Write<uint32_t>(this->value.length());
@@ -189,7 +189,7 @@ inline void LDFData<std::u16string>::WriteValue(RakNet::BitStream& packet) {
// The specialized version for bool
template<>
inline void LDFData<bool>::WriteValue(RakNet::BitStream& packet) {
inline void LDFData<bool>::WriteValue(RakNet::BitStream& packet) const {
packet.Write<uint8_t>(this->GetValueType());
packet.Write<uint8_t>(this->value);
@@ -197,7 +197,7 @@ inline void LDFData<bool>::WriteValue(RakNet::BitStream& packet) {
// The specialized version for std::string (UTF-8)
template<>
inline void LDFData<std::string>::WriteValue(RakNet::BitStream& packet) {
inline void LDFData<std::string>::WriteValue(RakNet::BitStream& packet) const {
packet.Write<uint8_t>(this->GetValueType());
packet.Write<uint32_t>(this->value.length());
@@ -206,18 +206,18 @@ inline void LDFData<std::string>::WriteValue(RakNet::BitStream& packet) {
}
}
template<> inline std::string LDFData<std::u16string>::GetValueString() {
template<> inline std::string LDFData<std::u16string>::GetValueString() const {
return GeneralUtils::UTF16ToWTF8(this->value, this->value.size());
}
template<> inline std::string LDFData<int32_t>::GetValueString() { return std::to_string(this->value); }
template<> inline std::string LDFData<float>::GetValueString() { return std::to_string(this->value); }
template<> inline std::string LDFData<double>::GetValueString() { return std::to_string(this->value); }
template<> inline std::string LDFData<uint32_t>::GetValueString() { return std::to_string(this->value); }
template<> inline std::string LDFData<bool>::GetValueString() { return std::to_string(this->value); }
template<> inline std::string LDFData<uint64_t>::GetValueString() { return std::to_string(this->value); }
template<> inline std::string LDFData<LWOOBJID>::GetValueString() { return std::to_string(this->value); }
template<> inline std::string LDFData<int32_t>::GetValueString() const { return std::to_string(this->value); }
template<> inline std::string LDFData<float>::GetValueString() const { return std::to_string(this->value); }
template<> inline std::string LDFData<double>::GetValueString() const { return std::to_string(this->value); }
template<> inline std::string LDFData<uint32_t>::GetValueString() const { return std::to_string(this->value); }
template<> inline std::string LDFData<bool>::GetValueString() const { return std::to_string(this->value); }
template<> inline std::string LDFData<uint64_t>::GetValueString() const { return std::to_string(this->value); }
template<> inline std::string LDFData<LWOOBJID>::GetValueString() const { return std::to_string(this->value); }
template<> inline std::string LDFData<std::string>::GetValueString() { return this->value; }
template<> inline std::string LDFData<std::string>::GetValueString() const { return this->value; }
#endif //!__LDFFORMAT__H__

View File

@@ -29,7 +29,7 @@ void Writer::Flush() {
FileWriter::FileWriter(const char* outpath) {
m_Outfile = fopen(outpath, "wt");
if (!m_Outfile) fmt::println("Couldn't open {:s} for writing!", outpath);
if (!m_Outfile) printf("Couldn't open %s for writing!\n", outpath);
m_Outpath = outpath;
m_IsConsoleWriter = false;
}

View File

@@ -1,14 +1,6 @@
#pragma once
// fmt includes:
#include <fmt/core.h>
#include <fmt/format.h>
#include <fmt/chrono.h>
// C++ includes:
#include <chrono>
#include <memory>
#include <source_location>
#include <string>
#include <vector>
@@ -23,88 +15,22 @@
// Calculate the filename at compile time from the path.
// We just do this by scanning the path for the last '/' or '\' character and returning the string after it.
constexpr const char* GetFileNameFromAbsolutePath(const char* path) {
const char* file = path;
while (*path) {
const char nextChar = *path++;
if (nextChar == '/' || nextChar == '\\') {
file = path;
}
}
return file;
}
/**
* Location wrapper class
* Used to implicitly forward source location information without adding a function parameter
*/
template <typename T>
class location_wrapper {
public:
// Constructor
template <typename U = T>
consteval location_wrapper(const U& val, const std::source_location& loc = std::source_location::current())
: m_File(GetFileNameFromAbsolutePath(loc.file_name()))
, m_Loc(loc)
, m_Obj(val) {
}
// Methods
[[nodiscard]] constexpr const char* file() const noexcept { return m_File; }
[[nodiscard]] constexpr const std::source_location& loc() const noexcept { return m_Loc; }
[[nodiscard]] constexpr const T& get() const noexcept { return m_Obj; }
protected:
const char* m_File{};
std::source_location m_Loc{};
T m_Obj{};
};
/**
* Logging functions (EXPERIMENTAL)
*/
namespace Log {
template <typename... Ts>
[[nodiscard]] inline tm Time() { // TODO: Move?
return fmt::localtime(std::time(nullptr));
}
template <typename... Ts>
using FormatString = location_wrapper<fmt::format_string<Ts...>>;
template <typename... Ts>
inline void Info(const FormatString<Ts...> fmt_str, Ts&&... args) {
fmt::print("[{:%d-%m-%y %H:%M:%S} {}:{}] ", Time(), fmt_str.file(), fmt_str.loc().line());
fmt::println(fmt_str.get(), std::forward<Ts>(args)...);
}
template <typename... Ts>
inline void Warn(const FormatString<Ts...> fmt_str, Ts&&... args) {
fmt::print("[{:%d-%m-%y %H:%M:%S} {}:{}] Warning: ", Time(), fmt_str.file(), fmt_str.loc().line());
fmt::println(fmt_str.get(), std::forward<Ts>(args)...);
}
template <typename... Ts>
inline void Debug(const FormatString<Ts...> fmt_str, Ts&&... args) {
// if (!m_logDebugStatements) return;
Log::Info(fmt_str, std::forward<Ts>(args)...);
}
const char* file = path;
while (*path) {
char nextChar = *path++;
if (nextChar == '/' || nextChar == '\\') {
file = path;
}
}
return file;
}
// These have to have a constexpr variable to store the filename_and_line result in a local variable otherwise
// they will not be valid constexpr and will be evaluated at runtime instead of compile time!
// The full string is still stored in the binary, however the offset of the filename in the absolute paths
// is used in the instruction instead of the start of the absolute path.
//#define LOG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->Log(str, message, ##__VA_ARGS__); } while(0)
//#define LOG(message, ...) do {\
const auto now = std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::now());\
fmt::println("[{:%d-%m-%y %H:%M:%S} {:s}] " message, now, FILENAME_AND_LINE, ##__VA_ARGS__);\
} while(0)
#define LOG(message, ...) Log::Info(message __VA_OPT__(,) __VA_ARGS__)
//#define LOG_DEBUG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->LogDebug(str, message, ##__VA_ARGS__); } while(0)
#define LOG_DEBUG(message, ...) Log::Debug(message __VA_OPT__(,) __VA_ARGS__)
#define LOG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->Log(str, message, ##__VA_ARGS__); } while(0)
#define LOG_DEBUG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->LogDebug(str, message, ##__VA_ARGS__); } while(0)
// Writer class for writing data to files.
class Writer {

73
dCommon/Observable.h Normal file
View File

@@ -0,0 +1,73 @@
#ifndef __OBSERVABLE_H__
#define __OBSERVABLE_H__
#include <vector>
#include <functional>
/**
* @brief An event which can be observed by multiple observers.
*
* @tparam T The types of the arguments to be passed to the observers.
*/
template <typename... T>
class Observable {
public:
typedef std::function<void(T...)> Observer;
/**
* @brief Adds an observer to the event.
*
* @param observer The observer to add.
*/
void AddObserver(const Observer& observer) {
observers.push_back(observer);
}
/**
* @brief Removes an observer from the event.
*
* @param observer The observer to remove.
*/
void RemoveObserver(const Observer& observer) {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
/**
* @brief Notifies all observers of the event.
*
* @param args The arguments to pass to the observers.
*/
void Notify(T... args) {
for (const auto& observer : observers) {
observer(args...);
}
}
/**
* += operator overload.
*/
Observable& operator+=(const Observer& observer) {
AddObserver(observer);
return *this;
}
/**
* -= operator overload.
*/
Observable& operator-=(const Observer& observer) {
RemoveObserver(observer);
return *this;
}
/**
* () operator overload.
*/
void operator()(T... args) {
Notify(args...);
}
private:
std::vector<Observer> observers;
};
#endif //!__OBSERVABLE_H__

View File

@@ -23,7 +23,7 @@ PackIndex::PackIndex(const std::filesystem::path& filePath) {
m_PackFileIndices.push_back(packFileIndex);
}
Log::Info("Loaded pack catalog with {:d} pack files and {:d} files", m_PackPaths.size(), m_PackFileIndices.size());
LOG("Loaded pack catalog with %i pack files and %i files", m_PackPaths.size(), m_PackFileIndices.size());
for (auto& item : m_PackPaths) {
std::replace(item.begin(), item.end(), '\\', '/');

View File

@@ -1,6 +1,7 @@
#include "dConfig.h"
#include <sstream>
#include <algorithm>
#include "BinaryPathFinder.h"
#include "GeneralUtils.h"

View File

@@ -6,10 +6,10 @@
namespace StringifiedEnum {
template<typename T>
constexpr std::string_view ToString(const T e) {
const std::string_view ToString(const T e) {
static_assert(std::is_enum_v<T>, "Not an enum"); // Check type
constexpr const auto& sv = magic_enum::enum_entries<T>();
constexpr auto& sv = magic_enum::enum_entries<T>();
const auto it = std::lower_bound(
sv.begin(), sv.end(), e,

View File

@@ -1,31 +0,0 @@
#ifndef __ECHATINTERNALMESSAGETYPE__H__
#define __ECHATINTERNALMESSAGETYPE__H__
#include <cstdint>
enum eChatInternalMessageType : uint32_t {
PLAYER_ADDED_NOTIFICATION = 0,
PLAYER_REMOVED_NOTIFICATION,
ADD_FRIEND,
ADD_BEST_FRIEND,
ADD_TO_TEAM,
ADD_BLOCK,
REMOVE_FRIEND,
REMOVE_BLOCK,
REMOVE_FROM_TEAM,
DELETE_TEAM,
REPORT,
PRIVATE_CHAT,
PRIVATE_CHAT_RESPONSE,
ANNOUNCEMENT,
MAIL_COUNT_UPDATE,
MAIL_SEND_NOTIFY,
REQUEST_USER_LIST,
FRIEND_LIST,
ROUTE_TO_PLAYER,
TEAM_UPDATE,
MUTE_UPDATE,
CREATE_TEAM,
};
#endif //!__ECHATINTERNALMESSAGETYPE__H__

View File

@@ -72,7 +72,9 @@ enum class eChatMessageType :uint32_t {
UPDATE_DONATION,
PRG_CSR_COMMAND,
HEARTBEAT_REQUEST_FROM_WORLD,
UPDATE_FREE_TRIAL_STATUS
UPDATE_FREE_TRIAL_STATUS,
// CUSTOM DLU MESSAGE ID FOR INTERNAL USE
CREATE_TEAM,
};
#endif //!__ECHATMESSAGETYPE__H__

View File

@@ -5,8 +5,7 @@ enum class eConnectionType : uint16_t {
SERVER = 0,
AUTH,
CHAT,
CHAT_INTERNAL,
WORLD,
WORLD = 4,
CLIENT,
MASTER
};

View File

@@ -790,9 +790,10 @@ enum class eGameMessageType : uint16_t {
GET_MISSION_TYPE_STATES = 853,
GET_TIME_PLAYED = 854,
SET_MISSION_VIEWED = 855,
SLASH_COMMAND_TEXT_FEEDBACK = 856,
HANDLE_SLASH_COMMAND_KORE_DEBUGGER = 857,
HKX_VEHICLE_LOADED = 856,
SLASH_COMMAND_TEXT_FEEDBACK = 857,
BROADCAST_TEXT_TO_CHATBOX = 858,
HANDLE_SLASH_COMMAND_KORE_DEBUGGER = 859,
OPEN_PROPERTY_MANAGEMENT = 860,
OPEN_PROPERTY_VENDOR = 861,
VOTE_ON_PROPERTY = 862,

View File

@@ -0,0 +1,59 @@
#ifndef __EWAYPOINTCOMMANDTYPES__H__
#define __EWAYPOINTCOMMANDTYPES__H__
#include <cstdint>
enum class eWaypointCommandType : uint32_t {
INVALID,
BOUNCE,
STOP,
GROUP_EMOTE,
SET_VARIABLE,
CAST_SKILL,
EQUIP_INVENTORY,
UNEQUIP_INVENTORY,
DELAY,
EMOTE,
TELEPORT,
PATH_SPEED,
REMOVE_NPC,
CHANGE_WAYPOINT,
DELETE_SELF,
KILL_SELF,
SPAWN_OBJECT,
PLAY_SOUND,
};
class WaypointCommandType {
public:
static eWaypointCommandType StringToWaypointCommandType(std::string commandString) {
const std::map<std::string, eWaypointCommandType> WaypointCommandTypeMap = {
{"bounce", eWaypointCommandType::BOUNCE},
{"stop", eWaypointCommandType::STOP},
{"groupemote", eWaypointCommandType::GROUP_EMOTE},
{"setvar", eWaypointCommandType::SET_VARIABLE},
{"castskill", eWaypointCommandType::CAST_SKILL},
{"eqInvent", eWaypointCommandType::EQUIP_INVENTORY},
{"unInvent", eWaypointCommandType::UNEQUIP_INVENTORY},
{"delay", eWaypointCommandType::DELAY},
{"femote", eWaypointCommandType::EMOTE},
{"emote", eWaypointCommandType::EMOTE},
{"teleport", eWaypointCommandType::TELEPORT},
{"pathspeed", eWaypointCommandType::PATH_SPEED},
{"removeNPC", eWaypointCommandType::REMOVE_NPC},
{"changeWP", eWaypointCommandType::CHANGE_WAYPOINT},
{"DeleteSelf", eWaypointCommandType::DELETE_SELF},
{"killself", eWaypointCommandType::KILL_SELF},
{"removeself", eWaypointCommandType::DELETE_SELF},
{"spawnOBJ", eWaypointCommandType::SPAWN_OBJECT},
{"playSound", eWaypointCommandType::PLAY_SOUND},
};
auto intermed = WaypointCommandTypeMap.find(commandString);
return (intermed != WaypointCommandTypeMap.end()) ? intermed->second : eWaypointCommandType::INVALID;
};
};
#endif //!__EWAYPOINTCOMMANDTYPES__H__

View File

@@ -29,8 +29,8 @@ enum class eWorldMessageType : uint32_t {
ROUTE_PACKET, // Social?
POSITION_UPDATE,
MAIL,
WORD_CHECK, // Whitelist word check
STRING_CHECK, // Whitelist string check
WORD_CHECK, // AllowList word check
STRING_CHECK, // AllowList string check
GET_PLAYERS_IN_ZONE,
REQUEST_UGC_MANIFEST_INFO,
BLUEPRINT_GET_ALL_DATA_REQUEST,

View File

@@ -25,6 +25,7 @@
#include "CDScriptComponentTable.h"
#include "CDSkillBehaviorTable.h"
#include "CDZoneTableTable.h"
#include "CDTamingBuildPuzzleTable.h"
#include "CDVendorComponentTable.h"
#include "CDActivitiesTable.h"
#include "CDPackageComponentTable.h"
@@ -41,8 +42,6 @@
#include "CDRewardCodesTable.h"
#include "CDPetComponentTable.h"
#include <exception>
#ifndef CDCLIENT_CACHE_ALL
// Uncomment this to cache the full cdclient database into memory. This will make the server load faster, but will use more memory.
// A vanilla CDClient takes about 46MB of memory + the regular world data.
@@ -55,13 +54,6 @@
#define CDCLIENT_DONT_CACHE_TABLE(x)
#endif
class CDClientConnectionException : public std::exception {
public:
virtual const char* what() const throw() {
return "CDClientDatabase is not connected!";
}
};
// Using a macro to reduce repetitive code and issues from copy and paste.
// As a note, ## in a macro is used to concatenate two tokens together.
@@ -108,11 +100,14 @@ DEFINE_TABLE_STORAGE(CDRewardCodesTable);
DEFINE_TABLE_STORAGE(CDRewardsTable);
DEFINE_TABLE_STORAGE(CDScriptComponentTable);
DEFINE_TABLE_STORAGE(CDSkillBehaviorTable);
DEFINE_TABLE_STORAGE(CDTamingBuildPuzzleTable);
DEFINE_TABLE_STORAGE(CDVendorComponentTable);
DEFINE_TABLE_STORAGE(CDZoneTableTable);
void CDClientManager::LoadValuesFromDatabase() {
if (!CDClientDatabase::isConnected) throw CDClientConnectionException();
if (!CDClientDatabase::isConnected) {
throw std::runtime_error{ "CDClientDatabase is not connected!" };
}
CDActivityRewardsTable::Instance().LoadValuesFromDatabase();
CDActivitiesTable::Instance().LoadValuesFromDatabase();
@@ -152,12 +147,13 @@ void CDClientManager::LoadValuesFromDatabase() {
CDRewardsTable::Instance().LoadValuesFromDatabase();
CDScriptComponentTable::Instance().LoadValuesFromDatabase();
CDSkillBehaviorTable::Instance().LoadValuesFromDatabase();
CDTamingBuildPuzzleTable::Instance().LoadValuesFromDatabase();
CDVendorComponentTable::Instance().LoadValuesFromDatabase();
CDZoneTableTable::Instance().LoadValuesFromDatabase();
}
void CDClientManager::LoadValuesFromDefaults() {
Log::Info("Loading default CDClient tables!");
LOG("Loading default CDClient tables!");
CDPetComponentTable::Instance().LoadValuesFromDefaults();
}

View File

@@ -50,14 +50,14 @@ void CDPetComponentTable::LoadValuesFromDatabase() {
}
void CDPetComponentTable::LoadValuesFromDefaults() {
GetEntriesMutable().insert(std::make_pair(defaultEntry.id, defaultEntry));
GetEntriesMutable().emplace(defaultEntry.id, defaultEntry);
}
CDPetComponent& CDPetComponentTable::GetByID(const uint32_t componentID) {
auto& entries = GetEntriesMutable();
auto itr = entries.find(componentID);
if (itr == entries.end()) {
Log::Warn("Unable to load pet component (ID {:d}) values from database! Using default values instead.", componentID);
LOG("Unable to load pet component (ID %i) values from database! Using default values instead.", componentID);
return defaultEntry;
}
return itr->second;

View File

@@ -0,0 +1,35 @@
#include "CDTamingBuildPuzzleTable.h"
void CDTamingBuildPuzzleTable::LoadValuesFromDatabase() {
// First, get the size of the table
uint32_t size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM TamingBuildPuzzles");
while (!tableSize.eof()) {
size = tableSize.getIntField(0, 0);
tableSize.nextRow();
}
// Reserve the size
auto& entries = GetEntriesMutable();
entries.reserve(size);
// Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM TamingBuildPuzzles");
while (!tableData.eof()) {
const auto lot = static_cast<LOT>(tableData.getIntField("NPCLot", LOT_NULL));
entries.emplace(lot, CDTamingBuildPuzzle{
.puzzleModelLot = lot,
.validPieces{ tableData.getStringField("ValidPiecesLXF") },
.timeLimit = static_cast<float>(tableData.getFloatField("Timelimit", 30.0f)),
.numValidPieces = tableData.getIntField("NumValidPieces", 6),
.imaginationCost = tableData.getIntField("imagCostPerBuild", 10)
});
tableData.nextRow();
}
}
const CDTamingBuildPuzzle* CDTamingBuildPuzzleTable::GetByLOT(const LOT lot) const {
const auto& entries = GetEntries();
const auto itr = entries.find(lot);
return itr != entries.cend() ? &itr->second : nullptr;
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include "CDTable.h"
/**
* Information for the minigame to be completed
*/
struct CDTamingBuildPuzzle {
UNUSED_COLUMN(uint32_t id = 0;)
// The LOT of the object that is to be created
LOT puzzleModelLot = LOT_NULL;
// The LOT of the NPC
UNUSED_COLUMN(LOT npcLot = LOT_NULL;)
// The .lxfml file that contains the bricks required to build the model
std::string validPieces{};
// The .lxfml file that contains the bricks NOT required to build the model
UNUSED_COLUMN(std::string invalidPieces{};)
// Difficulty value
UNUSED_COLUMN(int32_t difficulty = 1;)
// The time limit to complete the build
float timeLimit = 30.0f;
// The number of pieces required to complete the minigame
int32_t numValidPieces = 6;
// Number of valid pieces
UNUSED_COLUMN(int32_t totalNumPieces = 16;)
// Model name
UNUSED_COLUMN(std::string modelName{};)
// The .lxfml file that contains the full model
UNUSED_COLUMN(std::string fullModel{};)
// The duration of the pet taming minigame
UNUSED_COLUMN(float duration = 45.0f;)
// The imagination cost for the tamer to start the minigame
int32_t imaginationCost = 10;
};
class CDTamingBuildPuzzleTable : public CDTable<CDTamingBuildPuzzleTable, std::unordered_map<LOT, CDTamingBuildPuzzle>> {
public:
/**
* Load values from the CD client database
*/
void LoadValuesFromDatabase();
/**
* Gets the pet ability table corresponding to the pet LOT
* @returns A pointer to the corresponding table, or nullptr if one cannot be found
*/
[[nodiscard]]
const CDTamingBuildPuzzle* GetByLOT(const LOT lot) const;
};

View File

@@ -36,5 +36,6 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp"
"CDRewardsTable.cpp"
"CDScriptComponentTable.cpp"
"CDSkillBehaviorTable.cpp"
"CDTamingBuildPuzzleTable.cpp"
"CDVendorComponentTable.cpp"
"CDZoneTableTable.cpp" PARENT_SCOPE)

View File

@@ -15,7 +15,7 @@ target_include_directories(dDatabaseCDClient PUBLIC "."
"${PROJECT_SOURCE_DIR}/dCommon"
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"
)
target_link_libraries(dDatabaseCDClient PRIVATE fmt sqlite3)
target_link_libraries(dDatabaseCDClient PRIVATE sqlite3)
if (${CDCLIENT_CACHE_ALL})
add_compile_definitions(dDatabaseCDClient PRIVATE CDCLIENT_CACHE_ALL=${CDCLIENT_CACHE_ALL})

View File

@@ -4,5 +4,4 @@ add_subdirectory(GameDatabase)
add_library(dDatabase STATIC "MigrationRunner.cpp")
target_include_directories(dDatabase PUBLIC ".")
target_link_libraries(dDatabase
PUBLIC dDatabaseCDClient dDatabaseGame
PRIVATE fmt)
PUBLIC dDatabaseCDClient dDatabaseGame)

View File

@@ -16,7 +16,6 @@ target_include_directories(dDatabaseGame PUBLIC "."
)
target_link_libraries(dDatabaseGame
PUBLIC MariaDB::ConnCpp
PRIVATE fmt
INTERFACE dCommon)
# Glob together all headers that need to be precompiled

View File

@@ -13,7 +13,7 @@ namespace {
void Database::Connect() {
if (database) {
Log::Warn("Tried to connect to database when it's already connected!");
LOG("Tried to connect to database when it's already connected!");
return;
}
@@ -23,7 +23,7 @@ void Database::Connect() {
GameDatabase* Database::Get() {
if (!database) {
Log::Warn("Tried to get database when it's not connected!");
LOG("Tried to get database when it's not connected!");
Connect();
}
return database;
@@ -35,6 +35,6 @@ void Database::Destroy(std::string source) {
delete database;
database = nullptr;
} else {
Log::Warn("Trying to destroy database when it's not connected!");
LOG("Trying to destroy database when it's not connected!");
}
}

View File

@@ -30,7 +30,7 @@ namespace sql {
};
#ifdef _DEBUG
# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (sql::SQLException& ex) { Log::Warn("SQL Error: {:s}", ex.what()); throw; } } while(0)
# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (sql::SQLException& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0)
#else
# define DLU_SQL_TRY_CATCH_RETHROW(x) x
#endif // _DEBUG

View File

@@ -53,8 +53,8 @@ void MySQLDatabase::Connect() {
void MySQLDatabase::Destroy(std::string source) {
if (!con) return;
if (source.empty()) Log::Info("Destroying MySQL connection!");
else Log::Info("Destroying MySQL connection from {:s}!", source);
if (source.empty()) LOG("Destroying MySQL connection!");
else LOG("Destroying MySQL connection from %s!", source.c_str());
con->close();
delete con;
@@ -68,7 +68,7 @@ void MySQLDatabase::ExecuteCustomQuery(const std::string_view query) {
sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& query) {
if (!con) {
Connect();
Log::Info("Trying to reconnect to MySQL");
LOG("Trying to reconnect to MySQL");
}
if (!con->isValid() || con->isClosed()) {
@@ -77,7 +77,7 @@ sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& quer
con = nullptr;
Connect();
Log::Info("Trying to reconnect to MySQL from invalid or closed connection");
LOG("Trying to reconnect to MySQL from invalid or closed connection");
}
return con->prepareStatement(sql::SQLString(query.c_str(), query.length()));

View File

@@ -147,91 +147,91 @@ private:
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string_view param) {
// Log::Info("{}", param);
// LOG("%s", param.data());
stmt->setString(index, param.data());
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const char* param) {
// Log::Info("{}", param);
// LOG("%s", param);
stmt->setString(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string param) {
// Log::Info("{}", param);
stmt->setString(index, param);
// LOG("%s", param.c_str());
stmt->setString(index, param.c_str());
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int8_t param) {
// Log::Info("{}", param);
// LOG("%u", param);
stmt->setByte(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint8_t param) {
// Log::Info("{}", param);
// LOG("%d", param);
stmt->setByte(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int16_t param) {
// Log::Info("{}", param);
// LOG("%u", param);
stmt->setShort(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint16_t param) {
// Log::Info("{}", param);
// LOG("%d", param);
stmt->setShort(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint32_t param) {
// Log::Info("{}", param);
// LOG("%u", param);
stmt->setUInt(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int32_t param) {
// Log::Info("{}", param);
// LOG("%d", param);
stmt->setInt(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int64_t param) {
// Log::Info("{}", param);
// LOG("%llu", param);
stmt->setInt64(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint64_t param) {
// Log::Info("{}", param);
// LOG("%llu", param);
stmt->setUInt64(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const float param) {
// Log::Info({}", param);
// LOG("%f", param);
stmt->setFloat(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const double param) {
// Log::Info("{}", param);
// LOG("%f", param);
stmt->setDouble(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const bool param) {
// Log::Info("{}", param);
// LOG("%d", param);
stmt->setBoolean(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istream* param) {
// Log::Info("Blob");
// LOG("Blob");
// This is the one time you will ever see me use const_cast.
stmt->setBlob(index, const_cast<std::istream*>(param));
}
@@ -239,10 +239,10 @@ inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istr
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional<uint32_t> param) {
if (param) {
// Log::Info("{}", param.value());
// LOG("%d", param.value());
stmt->setInt(index, param.value());
} else {
// Log::Info("Null");
// LOG("Null");
stmt->setNull(index, sql::DataType::SQLNULL);
}
}

View File

@@ -38,8 +38,8 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr
0, // behavior 4. TODO implement this.
0 // behavior 5. TODO implement this.
);
} catch (const sql::SQLException& e) {
Log::Warn("Error inserting new property model: {:s}", e.what());
} catch (sql::SQLException& e) {
LOG("Error inserting new property model: %s", e.what());
}
}

View File

@@ -45,7 +45,7 @@ void MigrationRunner::RunMigrations() {
if (Database::Get()->IsMigrationRun(migration.name)) continue;
Log::Info("Running migration: {:s}", migration.name);
LOG("Running migration: %s", migration.name.c_str());
if (migration.name == "dlu/5_brick_model_sd0.sql") {
runSd0Migrations = true;
} else {
@@ -56,7 +56,7 @@ void MigrationRunner::RunMigrations() {
}
if (finalSQL.empty() && !runSd0Migrations) {
Log::Info("Server database is up to date.");
LOG("Server database is up to date.");
return;
}
@@ -67,7 +67,7 @@ void MigrationRunner::RunMigrations() {
if (query.empty()) continue;
Database::Get()->ExecuteCustomQuery(query.c_str());
} catch (sql::SQLException& e) {
Log::Info("Encountered error running migration: {:s}", e.what());
LOG("Encountered error running migration: %s", e.what());
}
}
}
@@ -75,9 +75,9 @@ void MigrationRunner::RunMigrations() {
// Do this last on the off chance none of the other migrations have been run yet.
if (runSd0Migrations) {
uint32_t numberOfUpdatedModels = BrickByBrickFix::UpdateBrickByBrickModelsToSd0();
Log::Info("{:d} models were updated from zlib to sd0.", numberOfUpdatedModels);
LOG("%i models were updated from zlib to sd0.", numberOfUpdatedModels);
uint32_t numberOfTruncatedModels = BrickByBrickFix::TruncateBrokenBrickByBrickXml();
Log::Info("{:d} models were truncated from the database.", numberOfTruncatedModels);
LOG("%i models were truncated from the database.", numberOfTruncatedModels);
}
}
@@ -111,14 +111,14 @@ void MigrationRunner::RunSQLiteMigrations() {
// Doing these 1 migration at a time since one takes a long time and some may think it is crashing.
// This will at the least guarentee that the full migration needs to be run in order to be counted as "migrated".
Log::Info("Executing migration: {:s}. This may take a while. Do not shut down server.", migration.name);
LOG("Executing migration: %s. This may take a while. Do not shut down server.", migration.name.c_str());
CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;");
for (const auto& dml : GeneralUtils::SplitString(migration.data, ';')) {
if (dml.empty()) continue;
try {
CDClientDatabase::ExecuteDML(dml.c_str());
} catch (CppSQLite3Exception& e) {
Log::Warn("Encountered error running DML command: ({:d}) : {:s}", e.errorCode(), e.errorMessage());
LOG("Encountered error running DML command: (%i) : %s", e.errorCode(), e.errorMessage());
}
}
@@ -129,5 +129,5 @@ void MigrationRunner::RunSQLiteMigrations() {
CDClientDatabase::ExecuteQuery("COMMIT;");
}
Log::Info("CDServer database is up to date.");
LOG("CDServer database is up to date.");
}

View File

@@ -0,0 +1,677 @@
#include "AdditionalEntityData.h"
#include "NejlikaData.h"
#include <DestroyableComponent.h>
#include <LevelProgressionComponent.h>
#include <InventoryComponent.h>
#include <BaseCombatAIComponent.h>
#include <TeamManager.h>
#include <ControllablePhysicsComponent.h>
#include <Item.h>
#include <queue>
using namespace nejlika;
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const
{
float total = 0;
for (const auto& modifier : activeModifiers) {
if (modifier.GetConvertTo() != ModifierType::Invalid) {
continue;
}
if (modifier.GetType() != type || modifier.GetOperator() != op || modifier.IsResistance() != resistance) {
continue;
}
total += modifier.GetValue();
}
return total;
}
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const {
float total = 0;
for (const auto& modifier : additionalModifiers) {
if (modifier.GetConvertTo() != ModifierType::Invalid) {
continue;
}
if (modifier.GetType() != type || modifier.GetOperator() != op || modifier.IsResistance() != resistance) {
continue;
}
total += modifier.GetValue();
}
return total + CalculateModifier(type, op, resistance);
}
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, int32_t level) const
{
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
if (!templateDataOpt.has_value()) {
return 0;
}
const auto& templateData = *templateDataOpt.value();
const auto scaler = templateData.GetScaler(type, false, level);
float additive = CalculateModifier(type, ModifierOperator::Additive, false);
if (scaler != 0 && additive >= scaler) {
additive -= scaler;
}
float multiplicative = CalculateModifier(type, ModifierOperator::Multiplicative, false);
return (scaler + additive) * (1 + multiplicative / 100);
}
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type) const {
return CalculateModifier(type, level);
}
float nejlika::AdditionalEntityData::CalculateFinalModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, int32_t level) const
{
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
if (!templateDataOpt.has_value()) {
return 0;
}
const auto& templateData = *templateDataOpt.value();
const auto scaler = templateData.GetScaler(type, false, level);
float additive = CalculateModifier(type, additionalModifiers, ModifierOperator::Additive, false);
if (scaler != 0 && additive >= scaler) {
additive -= scaler;
}
float multiplicative = CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false);
static const std::unordered_set<ModifierType> elementalDamage = {
ModifierType::Fire,
ModifierType::Cold,
ModifierType::Lightning
};
if (type == ModifierType::Health) {
if (lot == 1) additive += 25;
additive += CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false) * 2.5f;
additive += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 1.0f;
additive += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 1.0f;
}
else if (type == ModifierType::Imagination) {
additive += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 2.0f;
}
else if (type == ModifierType::Seperation || type == ModifierType::InternalDisassembly || type == ModifierType::Physical) {
multiplicative += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.33f;
}
else if (type == ModifierType::Pierce) {
multiplicative += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.285f;
}
else if (nejlika::IsOverTimeType(type) || nejlika::IsNormalDamageType(type)) {
multiplicative += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 0.33f;
}
else if (type == ModifierType::ImaginationRegen) {
const auto spirit = CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false);
additive += spirit * 0.01f + 1;
multiplicative += spirit * 0.25f;
}
else if (type == ModifierType::HealthRegen) {
const auto physique = CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false);
additive += physique * 0.04f;
}
else if (type == ModifierType::Defensive) {
additive += CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false) * 0.5f;
if (lot == 1) additive += level * 10;
}
else if (type == ModifierType::Offensive) {
additive += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.5f;
if (lot == 1) additive += level * 10;
}
if (elementalDamage.contains(type)) {
additive += CalculateModifier(ModifierType::Elemental, additionalModifiers, ModifierOperator::Additive, false) / elementalDamage.size();
multiplicative += CalculateModifier(ModifierType::Elemental, additionalModifiers, ModifierOperator::Multiplicative, false) / elementalDamage.size();
}
if (nejlika::IsNormalDamageType(type) || nejlika::IsOverTimeType(type)) {
additive += CalculateModifier(ModifierType::Damage, additionalModifiers, ModifierOperator::Additive, false);
multiplicative += CalculateModifier(ModifierType::Damage, additionalModifiers, ModifierOperator::Multiplicative, false);
}
float total = (scaler + additive) * (1 + multiplicative / 100);
return total;
}
float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) const
{
type = nejlika::GetResistanceType(type);
return CalculateModifier(type, ModifierOperator::Multiplicative, true);
}
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type) const
{
return 100 + CalculateModifier(type, ModifierOperator::Multiplicative, false);
}
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType, const TriggerParameters& params) {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return {};
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return {};
}
std::vector<ModifierInstance> result;
for (const auto& itemID : upgradeItems) {
auto* item = inventoryComponent->FindItemById(itemID);
if (item == nullptr) {
continue;
}
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(item->GetLot());
if (!upgradeDataOpt.has_value()) {
continue;
}
const auto& upgradeData = *upgradeDataOpt.value();
const auto modifiers = upgradeData.Trigger(item->GetCount(), triggerType, id, params);
result.insert(result.end(), modifiers.begin(), modifiers.end());
}
return result;
}
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType) {
return TriggerUpgradeItems(triggerType, {});
}
void nejlika::AdditionalEntityData::InitializeSkills() {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
struct entry {
LWOOBJID id;
int32_t priority;
entry(LWOOBJID id, int32_t priority) : id(id), priority(priority) {}
};
std::vector<entry> items;
for (const auto& itemID : upgradeItems) {
auto* item = inventoryComponent->FindItemById(itemID);
if (item == nullptr) {
continue;
}
const auto priority = item->GetSlot();
items.push_back(entry(itemID, priority));
}
std::sort(items.begin(), items.end(), [](const entry& a, const entry& b) {
return a.priority < b.priority;
});
for (const auto& item : items) {
AddSkills(item.id);
}
}
void nejlika::AdditionalEntityData::AddSkills(LWOOBJID item) {
if (!upgradeItems.contains(item)) {
return;
}
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
auto* itemData = inventoryComponent->FindItemById(item);
if (itemData == nullptr) {
return;
}
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(itemData->GetLot());
if (!upgradeDataOpt.has_value()) {
return;
}
const auto& upgradeData = *upgradeDataOpt.value();
LOG("Adding skills for item %i", id);
upgradeData.AddSkills(id);
}
void nejlika::AdditionalEntityData::RemoveSkills(LOT lot) {
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(lot);
if (!upgradeDataOpt.has_value()) {
return;
}
const auto& upgradeData = *upgradeDataOpt.value();
LOG("Removing skills for item %i", id);
upgradeData.RemoveSkills(id);
}
std::vector<ModifierInstance> nejlika::AdditionalEntityData::CalculateMainWeaponDamage() {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return {};
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return {};
}
Item* mainWeapon = nullptr;
for (const auto& [location, item] : inventoryComponent->GetEquippedItems()) {
if (location == "special_r") {
mainWeapon = inventoryComponent->FindItemById(item.id);
break;
}
}
if (mainWeapon == nullptr) {
return {};
}
const auto additionalItemDataOpt = NejlikaData::GetAdditionalItemData(mainWeapon->GetId());
if (!additionalItemDataOpt.has_value()) {
return {};
}
const auto& additionalItemData = *additionalItemDataOpt.value();
std::vector<ModifierInstance> result;
for (const auto& modifier : additionalItemData.GetModifierInstances()) {
if (nejlika::IsNormalDamageType(modifier.GetType()) || nejlika::IsOverTimeType(modifier.GetType()) || nejlika::IsDurationType(modifier.GetType())) {
if (modifier.GetOperator() == ModifierOperator::Additive && modifier.GetUpgradeName().empty()) {
result.push_back(modifier);
}
}
}
return result;
}
void nejlika::AdditionalEntityData::RollStandardModifiers(int32_t level) {
standardModifiers.clear();
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
if (templateDataOpt.has_value()) {
const auto& templateData = *templateDataOpt.value();
const auto modifiers = templateData.GenerateModifiers(level);
standardModifiers.insert(standardModifiers.end(), modifiers.begin(), modifiers.end());
}
const auto objectDataVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object);
for (const auto& objectData : objectDataVec) {
if (objectData.GetLOT() != lot) {
continue;
}
const auto modifiers = objectData.GenerateModifiers(level);
standardModifiers.insert(standardModifiers.end(), modifiers.begin(), modifiers.end());
}
}
void nejlika::AdditionalEntityData::TriggerPassiveRegeneration() {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
auto* destroyable = entity->GetComponent<DestroyableComponent>();
if (destroyable == nullptr) {
return;
}
const auto healthRegen = CalculateFinalModifier(ModifierType::HealthRegen, {}, level);
const auto imaginationRegen = CalculateFinalModifier(ModifierType::ImaginationRegen, {}, level);
if (healthRegen > 0) {
destroyable->SetHealth(std::min(destroyable->GetHealth() + static_cast<int32_t>(healthRegen), static_cast<int32_t>(destroyable->GetMaxHealth())));
}
if (imaginationRegen > 0) {
destroyable->SetImagination(std::min(destroyable->GetImagination() + static_cast<int32_t>(imaginationRegen), static_cast<int32_t>(destroyable->GetMaxImagination())));
}
Game::entityManager->SerializeEntity(entity);
// Trigger it again in 1 second
entity->AddCallbackTimer(1.0f, [this]() {
TriggerPassiveRegeneration();
});
}
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers) const {
return 100 + CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false);
}
std::unordered_map<ModifierType, std::unordered_map<ModifierType, float>> nejlika::AdditionalEntityData::CalculateDamageConversion(std::vector<ModifierInstance>& additionalModifiers) const {
std::unordered_map<ModifierType, std::unordered_map<ModifierType, float>> conversion;
for (const auto& modifier : activeModifiers) {
if (modifier.GetConvertTo() == ModifierType::Invalid) {
continue;
}
conversion[modifier.GetType()][modifier.GetConvertTo()] += modifier.GetValue();
}
for (const auto& modifier : additionalModifiers) {
if (modifier.GetConvertTo() == ModifierType::Invalid) {
continue;
}
conversion[modifier.GetType()][modifier.GetConvertTo()] += modifier.GetValue();
}
// Third pass: adjust bidirectional conversions
auto copy = conversion; // Create a copy to iterate over
for (const auto& [type, convertMap] : copy) {
for (const auto& [convertTo, value] : convertMap) {
if (conversion[convertTo][type] > 0) {
if (value > conversion[convertTo][type]) {
conversion[type][convertTo] -= conversion[convertTo][type];
conversion[convertTo][type] = 0; // Ensure no negative values
} else {
conversion[convertTo][type] -= value;
conversion[type][convertTo] = 0; // Ensure no negative values
}
}
}
}
// Fourth pass: if a type converts to multiple types, and the sum of the conversion values is greater than 100, normalize the values
for (const auto& [type, convertMap] : conversion) {
float sum = 0;
for (const auto& [convertTo, value] : convertMap) {
sum += value;
}
if (sum > 100) {
for (const auto& [convertTo, value] : convertMap) {
conversion[type][convertTo] = value / sum * 100;
}
}
}
return conversion;
}
void nejlika::AdditionalEntityData::ApplyToEntity() {
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
if (!templateDataOpt.has_value()) {
return;
}
const auto& templateData = *templateDataOpt.value();
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
auto* destroyable = entity->GetComponent<DestroyableComponent>();
if (destroyable == nullptr) {
return;
}
auto* levelProgression = entity->GetComponent<LevelProgressionComponent>();
if (levelProgression != nullptr) {
this->level = levelProgression->GetLevel();
}
else {
this->level = templateData.GetMinLevel();
}
if (!initialized) {
RollStandardModifiers(level);
}
activeModifiers = standardModifiers;
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent) {
for (const auto& [location, item] : inventoryComponent->GetEquippedItems()) {
const auto itemDataOpt = NejlikaData::GetAdditionalItemData(item.id);
if (!itemDataOpt.has_value()) {
continue;
}
const auto& itemData = *itemDataOpt.value();
const auto& itemModifiers = itemData.GetModifierInstances();
for (const auto& modifier : itemModifiers) {
if (nejlika::IsNormalDamageType(modifier.GetType()) || nejlika::IsOverTimeType(modifier.GetType()) || nejlika::IsDurationType(modifier.GetType())) {
if (modifier.GetOperator() == ModifierOperator::Additive && modifier.GetUpgradeName().empty()) {
continue;
}
}
activeModifiers.push_back(modifier);
}
}
for (const auto& upgradeItem : upgradeItems) {
auto* item = inventoryComponent->FindItemById(upgradeItem);
if (item == nullptr) {
continue;
}
LOG("Applying upgrade item %i", item->GetLot());
const auto itemDataOpt = NejlikaData::GetUpgradeTemplate(item->GetLot());
if (!itemDataOpt.has_value()) {
LOG("Upgrade item %i has no data", item->GetLot());
continue;
}
const auto& itemData = *itemDataOpt.value();
const auto& itemModifiers = itemData.GenerateModifiers(item->GetCount());
LOG("Upgrade item %i has %i modifiers with level %i", item->GetLot(), itemModifiers.size(), item->GetCount());
activeModifiers.insert(activeModifiers.end(), itemModifiers.begin(), itemModifiers.end());
}
}
destroyable->SetMaxHealth(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Health, {}, level)));
destroyable->SetMaxArmor(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Armor, {}, level)));
//if (!entity->IsPlayer()) {
destroyable->SetMaxImagination(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Imagination, {}, level)));
//}
if (initialized) {
return;
}
destroyable->SetHealth(destroyable->GetMaxHealth());
destroyable->SetArmor(destroyable->GetMaxArmor());
//if (!entity->IsPlayer()) {
destroyable->SetImagination(destroyable->GetMaxImagination());
//}
if (entity->IsPlayer()) {
auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>();
if (controllablePhysicsComponent) controllablePhysicsComponent->SetSpeedMultiplier(CalculateMultiplier(ModifierType::Speed) / 100.0f);
}
TriggerPassiveRegeneration();
initialized = true;
}
void nejlika::AdditionalEntityData::CheckForRescale(AdditionalEntityData* other) {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
if (entity->IsPlayer()) {
return;
}
auto* baseCombat = entity->GetComponent<BaseCombatAIComponent>();
if (baseCombat == nullptr) {
return;
}
const auto& threats = baseCombat->GetThreats();
int32_t totalThreats = 0;
int32_t totalLevel = 0;
for (const auto& [threat, _] : threats) {
const auto threatEntityOpt = NejlikaData::GetAdditionalEntityData(threat);
if (!threatEntityOpt.has_value()) {
continue;
}
const auto& threatEntity = *threatEntityOpt.value();
if (other->id == threatEntity.id) {
continue;
}
totalLevel += threatEntity.level;
totalThreats++;
}
if (other != nullptr) {
totalLevel += other->level;
totalThreats++;
auto* team = TeamManager::Instance()->GetTeam(other->id);
if (team != nullptr) {
for (const auto& member : team->members) {
const auto memberEntityOpt = NejlikaData::GetAdditionalEntityData(member);
if (!memberEntityOpt.has_value()) {
continue;
}
const auto& memberEntity = *memberEntityOpt.value();
if (other->id == memberEntity.id) {
continue;
}
totalLevel += memberEntity.level;
totalThreats++;
}
}
}
if (totalThreats == 0) {
return;
}
const auto averageLevel = totalLevel / totalThreats;
// Can't rescale to a lower level
if (averageLevel <= level) {
return;
}
level = averageLevel;
auto* destroyable = entity->GetComponent<DestroyableComponent>();
if (destroyable == nullptr) {
return;
}
float healthPercentage = destroyable->GetMaxHealth() == 0 ? 1 : static_cast<float>(destroyable->GetHealth()) / destroyable->GetMaxHealth();
float armorPercentage = destroyable->GetMaxArmor() == 0 ? 1 : static_cast<float>(destroyable->GetArmor()) / destroyable->GetMaxArmor();
float imaginationPercentage = destroyable->GetMaxImagination() == 0 ? 1 : static_cast<float>(destroyable->GetImagination()) / destroyable->GetMaxImagination();
RollStandardModifiers(level);
ApplyToEntity();
destroyable->SetHealth(static_cast<int32_t>(destroyable->GetMaxHealth() * healthPercentage));
destroyable->SetArmor(static_cast<int32_t>(destroyable->GetMaxArmor() * armorPercentage));
destroyable->SetImagination(static_cast<int32_t>(destroyable->GetMaxImagination() * imaginationPercentage));
LOG("Rescaled entity %i to level %d", entity->GetLOT(), level);
}

View File

@@ -0,0 +1,109 @@
#pragma once
#include <cstdint>
#include <vector>
#include "Entity.h"
#include "ModifierInstance.h"
#include "EntityTemplate.h"
#include "UpgradeTriggerType.h"
#include "TriggerParameters.h"
#include <unordered_set>
namespace nejlika
{
class AdditionalEntityData
{
public:
AdditionalEntityData() = default;
AdditionalEntityData(LWOOBJID id, LOT lot) : id(id), lot(lot) {}
float CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const;
float CalculateModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const;
float CalculateModifier(ModifierType type, int32_t level) const;
float CalculateModifier(ModifierType type) const;
float CalculateFinalModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, int32_t level) const;
float CalculateResistance(ModifierType type) const;
/**
* @brief Calculate the multiplier for a given modifier type. With a base value of 100 (%).
*
* @param type The modifier type.
* @return The multiplier.
*/
float CalculateMultiplier(ModifierType type) const;
/**
* @brief Calculate the multiplier for a given modifier type. With a base value of 100 (%).
*
* @param type The modifier type.
* @param additionalModifiers Additional modifiers to apply.
* @return The multiplier.
*/
float CalculateMultiplier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers) const;
/**
* @brief Calculate damage conversation mapping.
*
* @param additionalModifiers Additional modifiers to apply.
*
* @return The damage conversion mapping.
*/
std::unordered_map<ModifierType, std::unordered_map<ModifierType, float>> CalculateDamageConversion(std::vector<ModifierInstance>& additionalModifiers) const;
void ApplyToEntity();
void CheckForRescale(AdditionalEntityData* other);
int32_t GetLevel() const { return level; }
LWOOBJID GetID() const { return id; }
LOT GetLOT() const { return lot; }
const std::unordered_set<LWOOBJID>& GetUpgradeItems() const { return upgradeItems; }
void AddUpgradeItem(LWOOBJID id) { upgradeItems.insert(id); }
void RemoveUpgradeItem(LWOOBJID id) { upgradeItems.erase(id); }
std::vector<ModifierInstance> TriggerUpgradeItems(UpgradeTriggerType triggerType, const TriggerParameters& params);
std::vector<ModifierInstance> TriggerUpgradeItems(UpgradeTriggerType triggerType);
void InitializeSkills();
void AddSkills(LWOOBJID item);
void RemoveSkills(LOT lot);
const std::vector<ModifierInstance>& GetActiveModifiers() const { return activeModifiers; }
std::vector<ModifierInstance> CalculateMainWeaponDamage();
private:
void RollStandardModifiers(int32_t level);
void TriggerPassiveRegeneration();
bool initialized = false;
std::vector<ModifierInstance> standardModifiers;
std::vector<ModifierInstance> activeModifiers;
std::unordered_set<LWOOBJID> upgradeItems;
LWOOBJID id;
LOT lot;
int32_t level = 1;
};
}

View File

@@ -0,0 +1,240 @@
#include "AdditionalItemData.h"
#include "Item.h"
#include "eItemType.h"
#include "NejlikaData.h"
using namespace nejlika;
nejlika::AdditionalItemData::AdditionalItemData(Item* item)
{
const auto& config = item->GetConfig();
for (const auto& entry : config)
{
if (entry->GetKey() != u"modifiers")
{
continue;
}
const auto str = entry->GetValueAsString();
if (str.empty())
{
continue;
}
try
{
const auto json = nlohmann::json::parse(str);
Load(json);
}
catch (const nlohmann::json::exception& e)
{
std::cout << "Failed to parse additional item data: " << e.what() << std::endl;
}
}
}
nejlika::AdditionalItemData::AdditionalItemData(const nlohmann::json& json) {
Load(json);
}
void nejlika::AdditionalItemData::Load(const nlohmann::json& json) {
if (json.contains("names"))
{
for (const auto& name : json["names"])
{
modifierNames.emplace_back(name);
}
}
if (json.contains("instances"))
{
for (const auto& instance : json["instances"])
{
modifierInstances.emplace_back(instance);
}
}
}
nlohmann::json nejlika::AdditionalItemData::ToJson() const {
nlohmann::json json;
json["names"] = nlohmann::json::array();
for (const auto& name : modifierNames)
{
json["names"].push_back(name.ToJson());
}
json["instances"] = nlohmann::json::array();
for (const auto& instance : modifierInstances)
{
json["instances"].push_back(instance.ToJson());
}
return json;
}
void nejlika::AdditionalItemData::Save(Item* item) {
auto& config = item->GetConfig();
// Remove the old data
for (size_t i = 0; i < config.size(); i++)
{
if (config[i]->GetKey() == u"modifiers")
{
config.erase(config.begin() + i);
break;
}
}
std::stringstream ss;
ss << ToJson().dump();
std::cout << ss.str() << std::endl;
config.push_back(new LDFData<std::string>(u"modifiers", ToJson().dump()));
}
void nejlika::AdditionalItemData::RollModifiers(Item* item, int32_t level) {
modifierNames.clear();
modifierInstances.clear();
const auto& info = item->GetInfo();
const auto itemType = static_cast<eItemType>(info.itemType);
const auto itemRarity = info.rarity == 0 ? 1 : info.rarity;
uint32_t rarityRollPrefix = 0;
uint32_t rarityRollSuffix = 0;
// Generate (itemRarity) amout of names and modifiers rolls, take the highest rarity. 0-1000
for (int i = 0; i < itemRarity; i++) {
auto roll = GeneralUtils::GenerateRandomNumber<uint32_t>() % 1000;
if (roll > rarityRollPrefix) {
rarityRollPrefix = roll;
}
roll = GeneralUtils::GenerateRandomNumber<uint32_t>() % 1000;
if (roll > rarityRollSuffix) {
rarityRollSuffix = roll;
}
}
const auto& templates = NejlikaData::GetModifierNameTemplates();
std::vector<ModifierNameTemplate> availablePrefixes;
std::vector<ModifierNameTemplate> availableSuffixes;
for (const auto& [type, nameTemplates] : templates) {
for (const auto& nameTemplate : nameTemplates) {
if (type != ModifierNameType::Prefix && type != ModifierNameType::Suffix) {
continue;
}
if (nameTemplate.GetMinLevel() > level || nameTemplate.GetMaxLevel() < level) {
continue;
}
const auto rarity = nameTemplate.GetRarity();
if (rarity == ModifierRarity::Common) {
continue;
}
const auto& itemTypes = nameTemplate.GetItemTypes();
if (std::find(itemTypes.begin(), itemTypes.end(), itemType) == itemTypes.end()) {
continue;
}
/*
Uncommon: rarityRoll > 500,
Rare: rarityRoll > 900,
Epic: rarityRoll > 990,
Legendary: rarityRoll = 999
*/
const auto roll = type == ModifierNameType::Prefix ? rarityRollPrefix : rarityRollSuffix;
if (rarity == ModifierRarity::Uncommon && roll > 900) {
continue;
}
if (rarity == ModifierRarity::Rare && (roll <= 900 || roll > 990)) {
continue;
}
if (rarity == ModifierRarity::Epic && (roll <= 990 || roll > 998)) {
continue;
}
if (rarity == ModifierRarity::Legendary && roll != 999) {
continue;
}
if (type == ModifierNameType::Prefix) {
availablePrefixes.push_back(nameTemplate);
}
else {
availableSuffixes.push_back(nameTemplate);
}
}
}
if (!availablePrefixes.empty()) {
const auto& prefix = availablePrefixes[GeneralUtils::GenerateRandomNumber<uint32_t>() % availablePrefixes.size()];
modifierNames.push_back(ModifierName(prefix));
const auto modifiers = prefix.GenerateModifiers(level);
modifierInstances.insert(modifierInstances.end(), modifiers.begin(), modifiers.end());
}
if (!availableSuffixes.empty()) {
const auto& suffix = availableSuffixes[GeneralUtils::GenerateRandomNumber<uint32_t>() % availableSuffixes.size()];
modifierNames.push_back(ModifierName(suffix));
const auto modifiers = suffix.GenerateModifiers(level);
modifierInstances.insert(modifierInstances.end(), modifiers.begin(), modifiers.end());
}
const auto& itemTemplateVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object);
std::vector<const ModifierNameTemplate*> availableObjects;
for (const auto& itemTemplate : itemTemplateVec) {
if (itemTemplate.GetMinLevel() > level || itemTemplate.GetMaxLevel() < level) {
continue;
}
if (itemTemplate.GetLOT() != static_cast<int32_t>(item->GetLot())) {
continue;
}
availableObjects.push_back(&itemTemplate);
}
if (availableObjects.empty()) {
Save(item);
return;
}
const auto& itemTemplate = *availableObjects[GeneralUtils::GenerateRandomNumber<uint32_t>() % availableObjects.size()];
const auto itemModifiers = itemTemplate.GenerateModifiers(level);
modifierInstances.insert(modifierInstances.end(), itemModifiers.begin(), itemModifiers.end());
Save(item);
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include <cstdint>
#include "ModifierName.h"
#include "ModifierInstance.h"
#include "json.hpp"
class Item;
namespace nejlika
{
class AdditionalItemData
{
public:
AdditionalItemData() = default;
AdditionalItemData(Item* item);
AdditionalItemData(const nlohmann::json& json);
nlohmann::json ToJson() const;
void Load(const nlohmann::json& json);
void Save(Item* item);
void RollModifiers(Item* item, int32_t level);
const std::vector<ModifierName>& GetModifierNames() const { return modifierNames; }
const std::vector<ModifierInstance>& GetModifierInstances() const { return modifierInstances; }
private:
std::vector<ModifierName> modifierNames;
std::vector<ModifierInstance> modifierInstances;
};
}

View File

@@ -6,7 +6,24 @@ set(DGAME_SOURCES "Character.cpp"
"TeamManager.cpp"
"TradingManager.cpp"
"User.cpp"
"UserManager.cpp")
"UserManager.cpp"
"ModifierTemplate.cpp"
"ModifierInstance.cpp"
"ModifierRarity.cpp"
"ModifierType.cpp"
"ModifierScale.cpp"
"ModifierName.cpp"
"ModifierNameTemplate.cpp"
"NejlikaData.cpp"
"AdditionalItemData.cpp"
"EntityTemplate.cpp"
"AdditionalEntityData.cpp"
"NejlikaHooks.cpp"
"UpgradeTemplate.cpp"
"UpgradeEffect.cpp"
"Lookup.cpp"
"NejlikaHelpers.cpp"
)
include_directories(
${PROJECT_SOURCE_DIR}/dScripts

View File

@@ -27,12 +27,9 @@ Character::Character(uint32_t id, User* parentUser) {
m_ID = id;
m_ParentUser = parentUser;
m_OurEntity = nullptr;
m_Doc = nullptr;
}
Character::~Character() {
if (m_Doc) delete m_Doc;
m_Doc = nullptr;
m_OurEntity = nullptr;
m_ParentUser = nullptr;
}
@@ -55,8 +52,6 @@ void Character::UpdateInfoFromDatabase() {
m_ZoneInstanceID = 0; //These values don't really matter, these are only used on the char select screen and seem unused.
m_ZoneCloneID = 0;
m_Doc = nullptr;
//Quickly and dirtly parse the xmlData to get the info we need:
DoQuickXMLDataParse();
@@ -70,28 +65,23 @@ void Character::UpdateInfoFromDatabase() {
}
void Character::UpdateFromDatabase() {
if (m_Doc) delete m_Doc;
UpdateInfoFromDatabase();
}
void Character::DoQuickXMLDataParse() {
if (m_XMLData.size() == 0) return;
delete m_Doc;
m_Doc = new tinyxml2::XMLDocument();
if (!m_Doc) return;
if (m_Doc->Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) {
Log::Info("Loaded xmlData for character {:s} ({:d})!", m_Name, m_ID);
if (m_Doc.Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) {
LOG("Loaded xmlData for character %s (%i)!", m_Name.c_str(), m_ID);
} else {
Log::Warn("Failed to load xmlData!");
LOG("Failed to load xmlData!");
//Server::rakServer->CloseConnection(m_ParentUser->GetSystemAddress(), true);
return;
}
tinyxml2::XMLElement* mf = m_Doc->FirstChildElement("obj")->FirstChildElement("mf");
tinyxml2::XMLElement* mf = m_Doc.FirstChildElement("obj")->FirstChildElement("mf");
if (!mf) {
Log::Warn("Failed to find mf tag!");
LOG("Failed to find mf tag!");
return;
}
@@ -108,16 +98,16 @@ void Character::DoQuickXMLDataParse() {
mf->QueryAttribute("ess", &m_Eyes);
mf->QueryAttribute("ms", &m_Mouth);
tinyxml2::XMLElement* inv = m_Doc->FirstChildElement("obj")->FirstChildElement("inv");
tinyxml2::XMLElement* inv = m_Doc.FirstChildElement("obj")->FirstChildElement("inv");
if (!inv) {
Log::Warn("Char has no inv!");
LOG("Char has no inv!");
return;
}
tinyxml2::XMLElement* bag = inv->FirstChildElement("items")->FirstChildElement("in");
if (!bag) {
Log::Warn("Couldn't find bag0!");
LOG("Couldn't find bag0!");
return;
}
@@ -141,7 +131,7 @@ void Character::DoQuickXMLDataParse() {
}
tinyxml2::XMLElement* character = m_Doc->FirstChildElement("obj")->FirstChildElement("char");
tinyxml2::XMLElement* character = m_Doc.FirstChildElement("obj")->FirstChildElement("char");
if (character) {
character->QueryAttribute("cc", &m_Coins);
int32_t gm_level = 0;
@@ -205,7 +195,7 @@ void Character::DoQuickXMLDataParse() {
character->QueryAttribute("lzrw", &m_OriginalRotation.w);
}
auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag");
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (flags) {
auto* currentChild = flags->FirstChildElement();
while (currentChild) {
@@ -239,12 +229,10 @@ void Character::SetBuildMode(bool buildMode) {
}
void Character::SaveXMLToDatabase() {
if (!m_Doc) return;
//For metrics, we'll record the time it took to save:
auto start = std::chrono::system_clock::now();
tinyxml2::XMLElement* character = m_Doc->FirstChildElement("obj")->FirstChildElement("char");
tinyxml2::XMLElement* character = m_Doc.FirstChildElement("obj")->FirstChildElement("char");
if (character) {
character->SetAttribute("gm", static_cast<uint32_t>(m_GMLevel));
character->SetAttribute("cc", m_Coins);
@@ -266,11 +254,11 @@ void Character::SaveXMLToDatabase() {
}
auto emotes = character->FirstChildElement("ue");
if (!emotes) emotes = m_Doc->NewElement("ue");
if (!emotes) emotes = m_Doc.NewElement("ue");
emotes->DeleteChildren();
for (int emoteID : m_UnlockedEmotes) {
auto emote = m_Doc->NewElement("e");
auto emote = m_Doc.NewElement("e");
emote->SetAttribute("id", emoteID);
emotes->LinkEndChild(emote);
@@ -280,15 +268,15 @@ void Character::SaveXMLToDatabase() {
}
//Export our flags:
auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag");
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (!flags) {
flags = m_Doc->NewElement("flag"); //Create a flags tag if we don't have one
m_Doc->FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time
flags = m_Doc.NewElement("flag"); //Create a flags tag if we don't have one
m_Doc.FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time
}
flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes
for (std::pair<uint32_t, uint64_t> flag : m_PlayerFlags) {
auto* f = m_Doc->NewElement("f");
auto* f = m_Doc.NewElement("f");
f->SetAttribute("id", flag.first);
//Because of the joy that is tinyxml2, it doesn't offer a function to set a uint64 as an attribute.
@@ -301,7 +289,7 @@ void Character::SaveXMLToDatabase() {
// Prevents the news feed from showing up on world transfers
if (GetPlayerFlag(ePlayerFlag::IS_NEWS_SCREEN_VISIBLE)) {
auto* s = m_Doc->NewElement("s");
auto* s = m_Doc.NewElement("s");
s->SetAttribute("si", ePlayerFlag::IS_NEWS_SCREEN_VISIBLE);
flags->LinkEndChild(s);
}
@@ -310,7 +298,7 @@ void Character::SaveXMLToDatabase() {
//Call upon the entity to update our xmlDoc:
if (!m_OurEntity) {
Log::Warn("{:d}:{:s} didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName());
LOG("%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str());
return;
}
@@ -321,12 +309,12 @@ void Character::SaveXMLToDatabase() {
//For metrics, log the time it took to save:
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed = end - start;
Log::Info("{:d}:{:s} Saved character to Database in: {:f}s", this->GetID(), this->GetName(), elapsed.count());
LOG("%i:%s Saved character to Database in: %fs", this->GetID(), this->GetName().c_str(), elapsed.count());
}
void Character::SetIsNewLogin() {
// If we dont have a flag element, then we cannot have a s element as a child of flag.
auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag");
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (!flags) return;
auto* currentChild = flags->FirstChildElement();
@@ -334,7 +322,7 @@ void Character::SetIsNewLogin() {
auto* nextChild = currentChild->NextSiblingElement();
if (currentChild->Attribute("si")) {
flags->DeleteChild(currentChild);
Log::Info("Removed isLoggedIn flag from character {:d}:{:s}, saving character to database", GetID(), GetName());
LOG("Removed isLoggedIn flag from character %i:%s, saving character to database", GetID(), GetName().c_str());
WriteToDatabase();
}
currentChild = nextChild;
@@ -344,7 +332,7 @@ void Character::SetIsNewLogin() {
void Character::WriteToDatabase() {
//Dump our xml into m_XMLData:
tinyxml2::XMLPrinter printer(0, true, 0);
m_Doc->Print(&printer);
m_Doc.Print(&printer);
//Finally, save to db:
Database::Get()->UpdateCharacterXml(m_ID, printer.CStr());
@@ -421,15 +409,15 @@ void Character::SetRetroactiveFlags() {
void Character::SaveXmlRespawnCheckpoints() {
//Export our respawn points:
auto* points = m_Doc->FirstChildElement("obj")->FirstChildElement("res");
auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res");
if (!points) {
points = m_Doc->NewElement("res");
m_Doc->FirstChildElement("obj")->LinkEndChild(points);
points = m_Doc.NewElement("res");
m_Doc.FirstChildElement("obj")->LinkEndChild(points);
}
points->DeleteChildren();
for (const auto& point : m_WorldRespawnCheckpoints) {
auto* r = m_Doc->NewElement("r");
auto* r = m_Doc.NewElement("r");
r->SetAttribute("w", point.first);
r->SetAttribute("x", point.second.x);
@@ -443,7 +431,7 @@ void Character::SaveXmlRespawnCheckpoints() {
void Character::LoadXmlRespawnCheckpoints() {
m_WorldRespawnCheckpoints.clear();
auto* points = m_Doc->FirstChildElement("obj")->FirstChildElement("res");
auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res");
if (!points) {
return;
}

View File

@@ -37,7 +37,7 @@ public:
void LoadXmlRespawnCheckpoints();
const std::string& GetXMLData() const { return m_XMLData; }
tinyxml2::XMLDocument* GetXMLDoc() const { return m_Doc; }
const tinyxml2::XMLDocument& GetXMLDoc() const { return m_Doc; }
/**
* Out of abundance of safety and clarity of what this saves, this is its own function.
@@ -623,7 +623,7 @@ private:
/**
* The character XML belonging to this character
*/
tinyxml2::XMLDocument* m_Doc;
tinyxml2::XMLDocument m_Doc;
/**
* Title of an announcement this character made (reserved for GMs)

View File

@@ -96,6 +96,9 @@
#include "CDSkillBehaviorTable.h"
#include "CDZoneTableTable.h"
Observable<Entity*, const PositionUpdate&> Entity::OnPlayerPositionUpdate;
Observable<Entity*> Entity::OnReadyForUpdates;
Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Entity* parentEntity) {
m_ObjectID = objectID;
m_TemplateID = info.lot;
@@ -138,11 +141,11 @@ Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Enti
Entity::~Entity() {
if (IsPlayer()) {
Log::Info("Deleted player");
LOG("Deleted player");
// Make sure the player exists first. Remove afterwards to prevent the OnPlayerExist functions from not being able to find the player.
if (!PlayerManager::RemovePlayer(this)) {
Log::Warn("Unable to find player to remove from manager.");
LOG("Unable to find player to remove from manager.");
return;
}
@@ -476,8 +479,7 @@ void Entity::Initialize() {
}
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::INVENTORY) > 0 || m_Character) {
auto* xmlDoc = m_Character ? m_Character->GetXMLDoc() : nullptr;
AddComponent<InventoryComponent>(xmlDoc);
AddComponent<InventoryComponent>();
}
// if this component exists, then we initialize it. it's value is always 0
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MULTI_ZONE_ENTRANCE, -1) != -1) {
@@ -731,15 +733,21 @@ void Entity::Initialize() {
// if we have a moving platform path, then we need a moving platform component
if (path->pathType == PathType::MovingPlatform) {
AddComponent<MovingPlatformComponent>(pathName);
// else if we are a movement path
} /*else if (path->pathType == PathType::Movement) {
auto movementAIcomp = GetComponent<MovementAIComponent>();
if (movementAIcomp){
// TODO: set path in existing movementAIComp
} else if (path->pathType == PathType::Movement) {
auto movementAIcomponent = GetComponent<MovementAIComponent>();
if (movementAIcomponent && combatAiId == 0) {
movementAIcomponent->SetPath(pathName);
} else {
// TODO: create movementAIcomp and set path
MovementAIInfo moveInfo = MovementAIInfo();
moveInfo.movementType = "";
moveInfo.wanderChance = 0;
moveInfo.wanderRadius = 16;
moveInfo.wanderSpeed = 2.5f;
moveInfo.wanderDelayMax = 5;
moveInfo.wanderDelayMin = 2;
AddComponent<MovementAIComponent>(moveInfo);
}
}*/
}
} else {
// else we still need to setup moving platform if it has a moving platform comp but no path
int32_t movingPlatformComponentId = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MOVING_PLATFORM, -1);
@@ -1238,7 +1246,7 @@ void Entity::WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType
outBitStream.Write0();
}
void Entity::UpdateXMLDoc(tinyxml2::XMLDocument* doc) {
void Entity::UpdateXMLDoc(tinyxml2::XMLDocument& doc) {
//This function should only ever be called from within Character, meaning doc should always exist when this is called.
//Naturally, we don't include any non-player components in this update function.
@@ -1529,7 +1537,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) {
bool waitForDeathAnimation = false;
if (destroyableComponent) {
waitForDeathAnimation = destroyableComponent->GetDeathBehavior() == 0 && killType != eKillType::SILENT;
waitForDeathAnimation = !destroyableComponent->GetIsSmashable() && destroyableComponent->GetDeathBehavior() == 0 && killType != eKillType::SILENT;
}
// Live waited a hard coded 12 seconds for death animations of type 0 before networking destruction!
@@ -1629,10 +1637,8 @@ void Entity::PickupItem(const LWOOBJID& objectID) {
CDObjectSkillsTable* skillsTable = CDClientManager::GetTable<CDObjectSkillsTable>();
std::vector<CDObjectSkills> skills = skillsTable->Query([=](CDObjectSkills entry) {return (entry.objectTemplate == p.second.lot); });
for (CDObjectSkills skill : skills) {
CDSkillBehaviorTable* skillBehTable = CDClientManager::GetTable<CDSkillBehaviorTable>();
auto* skillComponent = GetComponent<SkillComponent>();
if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID());
if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID(), skill.castOnType, NiQuaternion(0, 0, 0, 0));
auto* missionComponent = GetComponent<MissionComponent>();
@@ -1775,6 +1781,12 @@ void Entity::SetOwnerOverride(const LWOOBJID value) {
m_OwnerOverride = value;
}
void Entity::SetPlayerReadyForUpdates() {
m_PlayerIsReadyForUpdates = true;
OnReadyForUpdates(this);
}
bool Entity::GetIsGhostingCandidate() const {
return m_IsGhostingCandidate;
}
@@ -1837,6 +1849,12 @@ const NiPoint3& Entity::GetPosition() const {
return vehicel->GetPosition();
}
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
return rigidBodyPhantomPhysicsComponent->GetPosition();
}
return NiPoint3Constant::ZERO;
}
@@ -1865,6 +1883,12 @@ const NiQuaternion& Entity::GetRotation() const {
return vehicel->GetRotation();
}
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
return rigidBodyPhantomPhysicsComponent->GetRotation();
}
return NiQuaternionConstant::IDENTITY;
}
@@ -1893,6 +1917,12 @@ void Entity::SetPosition(const NiPoint3& position) {
vehicel->SetPosition(position);
}
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
rigidBodyPhantomPhysicsComponent->SetPosition(position);
}
Game::entityManager->SerializeEntity(this);
}
@@ -1921,6 +1951,12 @@ void Entity::SetRotation(const NiQuaternion& rotation) {
vehicel->SetRotation(rotation);
}
auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>();
if (rigidBodyPhantomPhysicsComponent != nullptr) {
rigidBodyPhantomPhysicsComponent->SetRotation(rotation);
}
Game::entityManager->SerializeEntity(this);
}
@@ -2106,6 +2142,8 @@ void Entity::ProcessPositionUpdate(PositionUpdate& update) {
Game::entityManager->QueueGhostUpdate(GetObjectID());
if (updateChar) Game::entityManager->SerializeEntity(this);
OnPlayerPositionUpdate.Notify(this, update);
}
const SystemAddress& Entity::GetSystemAddress() const {

View File

@@ -11,6 +11,7 @@
#include "NiQuaternion.h"
#include "LDFFormat.h"
#include "eKillType.h"
#include "Observable.h"
namespace Loot {
class Info;
@@ -116,7 +117,7 @@ public:
void SetOwnerOverride(LWOOBJID value);
void SetPlayerReadyForUpdates() { m_PlayerIsReadyForUpdates = true; }
void SetPlayerReadyForUpdates();
void SetObservers(int8_t value);
@@ -174,7 +175,7 @@ public:
void WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacketType packetType);
void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType);
void UpdateXMLDoc(tinyxml2::XMLDocument* doc);
void UpdateXMLDoc(tinyxml2::XMLDocument& doc);
void Update(float deltaTime);
// Events
@@ -299,6 +300,13 @@ public:
// Scale will only be communicated to the client when the construction packet is sent
void SetScale(const float scale) { m_Scale = scale; };
/**
* @brief The observable for player entity position updates.
*/
static Observable<Entity*, const PositionUpdate&> OnPlayerPositionUpdate;
static Observable<Entity*> OnReadyForUpdates;
protected:
LWOOBJID m_ObjectID;

View File

@@ -26,6 +26,9 @@
#include "GhostComponent.h"
#include <ranges>
Observable<Entity*> EntityManager::OnEntityCreated;
Observable<Entity*> EntityManager::OnEntityDestroyed;
// Configure which zones have ghosting disabled, mostly small worlds.
std::vector<LWOMAPID> EntityManager::m_GhostingExcludedZones = {
// Small zones
@@ -138,6 +141,9 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
m_SpawnPoints.insert_or_assign(GeneralUtils::UTF16ToWTF8(spawnName), entity->GetObjectID());
}
// Notify observers that a new entity has been created
OnEntityCreated(entity);
return entity;
}
@@ -161,6 +167,9 @@ void EntityManager::DestroyEntity(Entity* entity) {
DestructEntity(entity);
}
// Notify observers that an entity is about to be destroyed
OnEntityDestroyed(entity);
// Delete this entity at the end of the frame
ScheduleForDeletion(id);
}
@@ -201,7 +210,7 @@ void EntityManager::KillEntities() {
auto* entity = GetEntity(toKill);
if (!entity) {
Log::Warn("Attempting to kill null entity {}", toKill);
LOG("Attempting to kill null entity %llu", toKill);
continue;
}
@@ -231,7 +240,7 @@ void EntityManager::DeleteEntities() {
if (ghostingToDelete != m_EntitiesToGhost.end()) m_EntitiesToGhost.erase(ghostingToDelete);
} else {
Log::Warn("Attempted to delete non-existent entity {}", toDelete);
LOG("Attempted to delete non-existent entity %llu", toDelete);
}
m_Entities.erase(toDelete);
}
@@ -322,7 +331,7 @@ const std::unordered_map<std::string, LWOOBJID>& EntityManager::GetSpawnPointEnt
void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr, const bool skipChecks) {
if (!entity) {
Log::Warn("Attempted to construct null entity");
LOG("Attempted to construct null entity");
return;
}

View File

@@ -7,6 +7,7 @@
#include <unordered_map>
#include "dCommonVars.h"
#include "Observable.h"
class Entity;
class EntityInfo;
@@ -72,6 +73,9 @@ public:
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
static Observable<Entity*> OnEntityCreated;
static Observable<Entity*> OnEntityDestroyed;
private:
void SerializeEntities();
void KillEntities();

90
dGame/EntityTemplate.cpp Normal file
View File

@@ -0,0 +1,90 @@
#include "EntityTemplate.h"
#include <magic_enum.hpp>
nejlika::EntityTemplate::EntityTemplate(const nlohmann::json& json) {
lot = json["lot"].get<LOT>();
minLevel = json.contains("min-level") ? json["min-level"].get<int32_t>() : 1;
for (const auto& scaler : json["scaling"])
{
EntityTemplateScaler s;
s.type = magic_enum::enum_cast<ModifierType>(scaler["type"].get<std::string>()).value();
s.isResistance = scaler.contains("resistance") && scaler["resistance"].get<bool>();
s.polynomial = scaler["polynomial"].get<std::vector<float>>();
scalers.push_back(s);
}
}
nlohmann::json nejlika::EntityTemplate::ToJson() const {
nlohmann::json json;
json["lot"] = lot;
json["min-level"] = minLevel;
nlohmann::json scalersJson;
for (const auto& scaler : scalers)
{
nlohmann::json s;
s["type"] = magic_enum::enum_name(scaler.type);
s["resistance"] = scaler.isResistance;
s["polynomial"] = scaler.polynomial;
scalersJson.push_back(s);
}
json["scaling"] = scalersJson;
return json;
}
float nejlika::EntityTemplate::GetScaler(ModifierType type, bool isResistance, int32_t level) const {
for (const auto& scaler : scalers)
{
if (scaler.type == type && scaler.isResistance == isResistance)
{
return CalculateScaler(scaler, level);
}
}
return 0.0f;
}
std::vector<nejlika::ModifierInstance> nejlika::EntityTemplate::GenerateModifiers(int32_t level) const {
std::vector<ModifierInstance> modifiers;
for (const auto& scaler : scalers)
{
ModifierInstance modifier(
scaler.type,
CalculateScaler(scaler, level),
ModifierOperator::Additive,
scaler.isResistance,
ModifierCategory::Player,
0,
"",
ModifierType::Invalid,
""
);
modifiers.push_back(modifier);
}
return modifiers;
}
float nejlika::EntityTemplate::CalculateScaler(const EntityTemplateScaler& scaler, int32_t level) const {
float result = 0.0f;
for (size_t i = 0; i < scaler.polynomial.size(); ++i)
{
result += scaler.polynomial[i] * std::pow(level, i);
}
return result;
}

47
dGame/EntityTemplate.h Normal file
View File

@@ -0,0 +1,47 @@
#pragma once
#include <cstdint>
#include <vector>
#include "Entity.h"
#include "ModifierType.h"
#include "ModifierInstance.h"
#include "json.hpp"
namespace nejlika
{
class EntityTemplate
{
public:
EntityTemplate() = default;
EntityTemplate(const nlohmann::json& json);
nlohmann::json ToJson() const;
LOT GetLOT() const { return lot; }
int32_t GetMinLevel() const { return minLevel; }
float GetScaler(ModifierType type, bool isResistance, int32_t level) const;
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
private:
struct EntityTemplateScaler
{
ModifierType type;
bool isResistance;
std::vector<float> polynomial;
};
float CalculateScaler(const EntityTemplateScaler& scaler, int32_t level) const;
LOT lot;
std::vector<EntityTemplateScaler> scalers;
int32_t minLevel;
};
}

View File

@@ -236,7 +236,7 @@ void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t r
baseLookup += std::to_string(static_cast<uint32_t>(this->relatedPlayer));
}
baseLookup += " LIMIT 1";
Log::Debug("query is {:s}", baseLookup);
LOG_DEBUG("query is %s", baseLookup.c_str());
std::unique_ptr<sql::PreparedStatement> baseQuery(Database::Get()->CreatePreppedStmt(baseLookup));
baseQuery->setInt(1, this->gameID);
std::unique_ptr<sql::ResultSet> baseResult(baseQuery->executeQuery());
@@ -251,7 +251,7 @@ void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t r
int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd);
DluAssert(res != -1);
std::unique_ptr<sql::PreparedStatement> query(Database::Get()->CreatePreppedStmt(lookupBuffer.get()));
Log::Debug("Query is {:s} vars are {:d} {:d} {:d}", lookupBuffer.get(), this->gameID, this->relatedPlayer, relatedPlayerLeaderboardId);
LOG_DEBUG("Query is %s vars are %i %i %i", lookupBuffer.get(), this->gameID, this->relatedPlayer, relatedPlayerLeaderboardId);
query->setInt(1, this->gameID);
if (this->infoType == InfoType::Friends) {
query->setInt(2, this->relatedPlayer);
@@ -358,7 +358,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi
}
case Leaderboard::Type::None:
default:
Log::Warn("Unknown leaderboard type {:d} for game {:d}. Cannot save score!", GeneralUtils::ToUnderlying(leaderboardType), activityId);
LOG("Unknown leaderboard type %i for game %i. Cannot save score!", leaderboardType, activityId);
return;
}
bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore;
@@ -377,7 +377,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi
} else {
saveQuery = FormatInsert(leaderboardType, newScore, false);
}
Log::Info("save query {:s} {:d} {:d}", saveQuery, playerID, activityId);
LOG("save query %s %i %i", saveQuery.c_str(), playerID, activityId);
std::unique_ptr<sql::PreparedStatement> saveStatement(Database::Get()->CreatePreppedStmt(saveQuery));
saveStatement->setInt(1, playerID);
saveStatement->setInt(2, activityId);

181
dGame/Lookup.cpp Normal file
View File

@@ -0,0 +1,181 @@
#include "Lookup.h"
#include <fstream>
#include "json.hpp"
#include <iostream>
using namespace nejlika;
Lookup::Lookup(const std::filesystem::path& lookup)
{
m_Lookup = lookup;
// Check if the file exists.
if (!std::filesystem::exists(lookup))
{
// Empty lookup
return;
}
// Read the json file
std::ifstream file(lookup);
std::string json((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
// Parse the json
auto doc = nlohmann::json::parse(json);
// The document is a map, so we can iterate over it.
for (const auto& [key, v] : doc.items()) {
if (v.is_number_integer())
{
m_LookupMap[key] = v.get<int64_t>();
continue;
}
if (!v.is_object()) {
std::stringstream ss;
ss << "Invalid value for key \"" << key << "\" in lookup.";
throw std::runtime_error(ss.str());
}
// Get the data
auto data = v.get<nlohmann::json>();
// Get the id
auto id = data["id"].get<int64_t>();
m_LookupMap[key] = id;
// Get the metadata
auto metadata = data["metadata"].get<std::string>();
m_Metadata[key] = metadata;
}
}
nejlika::Lookup::Lookup(const Lookup &other)
{
m_Lookup = other.m_Lookup;
m_LookupMap = other.m_LookupMap;
m_Metadata = other.m_Metadata;
m_CoreSymbols = other.m_CoreSymbols;
}
id Lookup::GetValue(const name& symbol) const
{
id value;
if (IsCoreSymbol(symbol, value)) {
return value;
}
const auto& it = m_LookupMap.find(symbol);
if (it == m_LookupMap.end())
{
std::stringstream ss;
ss << "Symbol \"" << symbol << "\" does not exist in the lookup.";
throw std::runtime_error(ss.str());
}
return it->second;
}
bool nejlika::Lookup::Exists(const name &symbol) const
{
return IsCoreSymbol(symbol) || (m_CoreSymbols.find(symbol) != m_CoreSymbols.end()) || (m_LookupMap.find(symbol) != m_LookupMap.end());
}
bool nejlika::Lookup::Exists(id value) const
{
for (const auto& [k, v] : m_LookupMap)
{
if (v == value)
{
return true;
}
}
return false;
}
const std::string& Lookup::GetMetadata(const name& symbol) const
{
const auto& it = m_Metadata.find(symbol);
if (it == m_Metadata.end())
{
static std::string empty;
return empty;
}
return it->second;
}
const std::filesystem::path& Lookup::GetLookup() const
{
return m_Lookup;
}
const std::unordered_map<name, id> &nejlika::Lookup::GetMap() const
{
return m_LookupMap;
}
bool nejlika::Lookup::IsCoreSymbol(const name &symbol, id &value) const
{
try {
value = std::stoi(symbol);
return true;
} catch (...) {
// cont...
}
if (!symbol.starts_with(core_prefix)) {
return false;
}
// Check in the core symbols
const auto& it = m_CoreSymbols.find(symbol);
if (it != m_CoreSymbols.end())
{
value = it->second;
return true;
}
// In the format "core:<value>"
try {
value = std::stoi(symbol.substr(core_prefix.size() + 1));
} catch (...) {
return false;
}
return true;
}
bool nejlika::Lookup::IsCoreSymbol(const name &symbol)
{
// Check if it can be converted to an integer
try {
[[maybe_unused]] auto value = std::stoi(symbol);
return true;
} catch (...) {
// cont...
}
return symbol.starts_with(core_prefix);
}

117
dGame/Lookup.h Normal file
View File

@@ -0,0 +1,117 @@
#pragma once
#include <filesystem>
#include <functional>
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
namespace nejlika {
typedef std::string name;
typedef int id;
/**
* @brief A one-way mapping between symbols (names) and their corresponding numerical values.
*
* This is an active lookup, meaning that it is possible to add new symbols and their values to the lookup.
*
* A method is provided to wait for a symbol to be added to the lookup.
*/
class Lookup
{
public:
inline static const name core_prefix = "lego-universe";
/**
* @brief Constructs a Lookup object with the specified lookup file path.
*
* @param lookup The path to the lookup file.
* @throw If the lookup file could not be parsed.
*/
Lookup(const std::filesystem::path& lookup);
Lookup(const Lookup& other);
Lookup() = default;
/**
* @brief Gets the value of the specified symbol.
*
* @param symbol The symbol to get the value of.
* @return The value of the specified symbol.
* @throw If the specified symbol does not exist.
*/
id GetValue(const name& symbol) const;
/**
* @brief Checks whether the specified symbol exists.
*
* @param symbol The symbol to check.
* @return Whether the specified symbol exists.
*/
bool Exists(const name& symbol) const;
/**
* @brief Checks whether any symbol has the specified value.
*
* @param value The value to check.
* @return Whether any symbol has the specified value.
*/
bool Exists(id value) const;
/**
* @brief Gets the metadata of a specified symbol.
*
* @param symbol The symbol to get metadata of.
* @return The metadata of the specified symbol or an empty string if the symbol does not exist.
*/
const std::string& GetMetadata(const name& symbol) const;
/**
* @brief Gets the path to the lookup file.
*
* @return The path to the lookup file.
*/
const std::filesystem::path& GetLookup() const;
/**
* @brief Gets the map of all symbols and their values.
*
* @return The map of all symbols and their values.
*/
const std::unordered_map<name, id>& GetMap() const;
/**
* @brief Checks whether the specified symbol is a core symbol.
*
* @param symbol The symbol to check.
* @param value The value of the core symbol.
* @return Whether the specified symbol is a core symbol.
*/
bool IsCoreSymbol(const name& symbol, id& value) const;
/**
* @brief Checks whether the specified symbol is a core symbol.
*
* A symbol is considered a core symbol if it is either:
* a number;
* or a string starting with the core_prefix.
*
* @param symbol The symbol to check.
* @return Whether the specified symbol is a core symbol.
*/
static bool IsCoreSymbol(const name& symbol);
private:
std::filesystem::path m_Lookup;
std::unordered_map<name, id> m_LookupMap;
std::unordered_map<name, std::string> m_Metadata;
std::unordered_map<name, id> m_CoreSymbols;
};
} // namespace nejlika

14
dGame/ModifierCategory.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
namespace nejlika
{
enum class ModifierCategory : uint8_t
{
Player = 0 << 0,
Pet = 1 << 0
};
}

170
dGame/ModifierInstance.cpp Normal file
View File

@@ -0,0 +1,170 @@
#include "ModifierInstance.h"
#include <sstream>
#include <magic_enum.hpp>
nejlika::ModifierInstance::ModifierInstance(const nlohmann::json& config) {
type = magic_enum::enum_cast<ModifierType>(config["type"].get<std::string>()).value_or(ModifierType::Invalid);
convertTo = magic_enum::enum_cast<ModifierType>(config["convert-to"].get<std::string>()).value_or(ModifierType::Invalid);
value = config["value"].get<float>();
if (config.contains("op")) {
op = magic_enum::enum_cast<ModifierOperator>(config["op"].get<std::string>()).value_or(ModifierOperator::Additive);
}
else {
op = ModifierOperator::Additive;
}
isResistance = config.contains("resistance") ? config["resistance"].get<bool>() : false;
if (config.contains("category")) {
category = magic_enum::enum_cast<ModifierCategory>(config["category"].get<std::string>()).value_or(ModifierCategory::Player);
}
else {
category = ModifierCategory::Player;
}
effectID = config.contains("effect-id") ? config["effect-id"].get<uint32_t>() : 0;
effectType = config.contains("effect-type") ? config["effect-type"].get<std::string>() : "";
}
nlohmann::json nejlika::ModifierInstance::ToJson() const
{
nlohmann::json config;
config["type"] = magic_enum::enum_name(type);
config["convert-to"] = magic_enum::enum_name(convertTo);
config["value"] = value;
config["op"] = magic_enum::enum_name(op);
config["resistance"] = isResistance;
config["category"] = magic_enum::enum_name(category);
config["effect-id"] = effectID;
config["effect-type"] = effectType;
return config;
}
std::string nejlika::ModifierInstance::GenerateHtmlString(const std::vector<ModifierInstance>& modifiers)
{
std::stringstream ss;
// target -> resistance -> op -> type -> value
std::unordered_map<ModifierCategory, std::unordered_map<bool, std::unordered_map<ModifierOperator, std::unordered_map<ModifierType, float>>>> modifierMap;
bool hasConvertTo = false;
bool hasSkillModifier = false;
for (const auto& modifier : modifiers) {
if (modifier.type == ModifierType::Invalid) {
continue;
}
if (modifier.GetConvertTo() != ModifierType::Invalid)
{
hasConvertTo = true;
continue;
}
if (!modifier.GetUpgradeName().empty())
{
hasSkillModifier = true;
continue;
}
modifierMap[modifier.category][modifier.isResistance][modifier.op][modifier.type] = modifier.value;
}
// Resistances and addatives are not separated, pet and player are
// Summarize the resistances and addatives
for (const auto& target : modifierMap) {
if (target.first == ModifierCategory::Pet) {
ss << "\n<font color=\"#D0AB62\">Pets:</font>\n";
}
for (const auto& resistance : target.second) {
for (const auto& math : resistance.second) {
for (const auto& modifier : math.second) {
ss << "<font color=\"#FFFFFF\">";
ss << ((modifier.second > 0) ? (math.first == ModifierOperator::Multiplicative ? "+" : "") : "-");
ss << std::fixed << std::setprecision(1) << std::abs(modifier.second);
if (math.first == ModifierOperator::Multiplicative) {
ss << "%";
}
ss << "</font> <font color=\"#D0AB62\">";
ss << " " << nejlika::GetModifierTypeName(modifier.first);
if (resistance.first) {
// If the ss now ends with 'Damage' remove it
if (ss.str().substr(ss.str().size() - 7) == " Damage") {
ss.seekp(-7, std::ios_base::end);
}
ss << " " << "Resistance";
}
ss << "</font>\n";
}
}
}
}
if (hasSkillModifier)
{
for (const auto& modifier : modifiers) {
if (modifier.type != ModifierType::SkillModifier) {
continue;
}
ss << "<font color=\"" << GetModifierTypeColor(modifier.type) << "\">";
ss << ((modifier.value > 0) ? "+" : "-");
ss << std::fixed << std::setprecision(0) << std::abs(modifier.value);
ss << " to ";
ss << modifier.GetUpgradeName();
ss << "</font>\n";
}
}
if (hasConvertTo)
{
for (const auto& modifier : modifiers) {
if (modifier.GetConvertTo() == ModifierType::Invalid)
{
continue;
}
if (modifier.type == ModifierType::Invalid) {
continue;
}
ss << "<font color=\"#FFFFFF\">";
// +xx/yy% of T1 converted to T2
ss << ((modifier.value > 0) ? "" : "-");
ss << std::fixed << std::setprecision(0) << std::abs(modifier.value);
ss << "%</font> <font color=\"#D0AB62\">";
ss << " of ";
ss << nejlika::GetModifierTypeName(modifier.type);
ss << " converted to ";
ss << nejlika::GetModifierTypeName(modifier.GetConvertTo());
ss << "</font>\n";
}
}
return ss.str();
}

97
dGame/ModifierInstance.h Normal file
View File

@@ -0,0 +1,97 @@
#pragma once
#include "ModifierType.h"
#include "ModifierCategory.h"
#include "ModifierOperator.h"
#include <cstdint>
#include <string>
#include "json.hpp"
namespace nejlika
{
class ModifierInstance
{
public:
ModifierInstance(
ModifierType type, float value, ModifierOperator op, bool isResistance, ModifierCategory category, uint32_t effectID, const std::string& effectType, ModifierType convertTo,
const std::string& upgradeName
) : type(type), value(value), op(op), isResistance(isResistance), category(category), effectID(effectID), effectType(effectType), convertTo(convertTo),
upgradeName(upgradeName)
{}
/**
* @brief Construct a new Modifier Instance object from a json configuration.
*
* @param config The json configuration.
*/
ModifierInstance(const nlohmann::json& config);
/**
* @brief Convert the modifier instance to a json representation.
*
* @return The json representation.
*/
nlohmann::json ToJson() const;
/**
* @brief Generate a HTML string representation of a set of modifiers.
*
* @param modifiers The modifiers to generate the HTML string for.
* @return The HTML string.
*/
static std::string GenerateHtmlString(const std::vector<ModifierInstance>& modifiers);
// Getters and setters
ModifierType GetType() const { return type; }
ModifierType GetConvertTo() const { return convertTo; }
float GetValue() const { return value; }
ModifierOperator GetOperator() const { return op; }
bool IsResistance() const { return isResistance; }
ModifierCategory GetCategory() const { return category; }
uint32_t GetEffectID() const { return effectID; }
std::string GetEffectType() const { return effectType; }
std::string GetUpgradeName() const { return upgradeName; }
void SetType(ModifierType type) { this->type = type; }
void SetConvertTo(ModifierType convertTo) { this->convertTo = convertTo; }
void SetValue(float value) { this->value = value; }
void SetOperator(ModifierOperator op) { this->op = op; }
void SetIsResistance(bool isResistance) { this->isResistance = isResistance; }
void SetCategory(ModifierCategory category) { this->category = category; }
void SetEffectID(uint32_t effectID) { this->effectID = effectID; }
void SetEffectType(const std::string& effectType) { this->effectType = effectType; }
void SetUpgradeName(const std::string& upgradeName) { this->upgradeName = upgradeName; }
private:
ModifierType type;
ModifierType convertTo;
float value;
ModifierOperator op;
bool isResistance;
ModifierCategory category;
uint32_t effectID;
std::string effectType;
std::string upgradeName;
};
}

83
dGame/ModifierName.cpp Normal file
View File

@@ -0,0 +1,83 @@
#include "ModifierName.h"
#include <magic_enum.hpp>
using namespace nejlika;
nejlika::ModifierName::ModifierName(const nlohmann::json & json)
{
name = json["name"].get<std::string>();
if (json.contains("type"))
{
type = magic_enum::enum_cast<ModifierNameType>(json["type"].get<std::string>()).value_or(ModifierNameType::Prefix);
}
else
{
type = ModifierNameType::Prefix;
}
if (json.contains("rarity"))
{
rarity = magic_enum::enum_cast<ModifierRarity>(json["rarity"].get<std::string>()).value_or(ModifierRarity::Common);
}
else
{
rarity = ModifierRarity::Common;
}
}
nejlika::ModifierName::ModifierName(const ModifierNameTemplate& templateData) {
name = templateData.GetName();
type = templateData.GetType();
rarity = templateData.GetRarity();
}
nlohmann::json nejlika::ModifierName::ToJson() const
{
nlohmann::json json;
json["name"] = name;
json["type"] = magic_enum::enum_name(type);
json["rarity"] = magic_enum::enum_name(rarity);
return json;
}
std::string nejlika::ModifierName::GenerateHtmlString() const {
const auto& rarityColor = ModifierRarityHelper::GetModifierRarityColor(rarity);
std::stringstream ss;
ss << "<font color=\"" << rarityColor << "\">" << name << "</font>";
return ss.str();
}
std::string nejlika::ModifierName::GenerateHtmlString(const std::vector<ModifierName>& names)
{
std::stringstream ss;
for (const auto& name : names) {
if (name.GetType() == ModifierNameType::Prefix && !name.name.empty()) {
ss << name.GenerateHtmlString() << "\n";
}
}
ss << "<font color=\"#D0AB62\">NAME</font>";
for (const auto& name : names) {
if (name.GetType() == ModifierNameType::Suffix && !name.name.empty()) {
ss << "\n" << name.GenerateHtmlString();
}
}
// Remove the last newline
auto str = ss.str();
if (!str.empty() && str.back() == '\n') {
str.pop_back();
}
return str;
}

57
dGame/ModifierName.h Normal file
View File

@@ -0,0 +1,57 @@
#pragma once
#include <cstdint>
#include <string>
#include "ModifierNameType.h"
#include "ModifierRarity.h"
#include "ModifierNameTemplate.h"
#include "json.hpp"
namespace nejlika
{
class ModifierName
{
public:
ModifierName(const std::string& name, ModifierNameType type, ModifierRarity rarity) :
name(name), type(type), rarity(rarity) {}
ModifierName(const nlohmann::json& json);
ModifierName(const ModifierNameTemplate& templateData);
nlohmann::json ToJson() const;
std::string GenerateHtmlString() const;
/**
* @brief Generate a HTML string representation of a set of names.
*
* @param modifiers The names to generate the HTML string for.
* @return The HTML string.
*/
static std::string GenerateHtmlString(const std::vector<ModifierName>& names);
// Getters and setters
const std::string& GetName() const { return name; }
ModifierNameType GetType() const { return type; }
ModifierRarity GetRarity() const { return rarity; }
void SetName(const std::string& name) { this->name = name; }
void SetType(ModifierNameType type) { this->type = type; }
void SetRarity(ModifierRarity rarity) { this->rarity = rarity; }
private:
std::string name;
ModifierNameType type;
ModifierRarity rarity;
};
}

View File

@@ -0,0 +1,166 @@
#include "ModifierNameTemplate.h"
#include <iostream>
#include "magic_enum.hpp"
using namespace nejlika;
nejlika::ModifierNameTemplate::ModifierNameTemplate(const nlohmann::json & json)
{
name = json["name"].get<std::string>();
if (json.contains("lot"))
{
lot = json["lot"].get<int32_t>();
}
else
{
lot = 0;
}
if (json.contains("type"))
{
type = magic_enum::enum_cast<ModifierNameType>(json["type"].get<std::string>()).value_or(ModifierNameType::Prefix);
}
else
{
type = ModifierNameType::Prefix;
}
if (json.contains("items"))
{
for (const auto& itemType : json["items"])
{
std::string type = itemType.get<std::string>();
// Make uppercase
std::transform(type.begin(), type.end(), type.begin(), ::toupper);
// Replace spaces with underscores
std::replace(type.begin(), type.end(), ' ', '_');
const auto itemTypeEnum = magic_enum::enum_cast<eItemType>(type);
if (itemTypeEnum.has_value())
{
itemTypes.push_back(itemTypeEnum.value());
}
else
{
std::cout << "Invalid item type: " << type << std::endl;
}
}
}
if (json.contains("modifiers"))
{
for (const auto& modifier : json["modifiers"])
{
const auto modifierTemplate = ModifierTemplate(modifier);
if (modifierTemplate.GetTypes().empty() || modifierTemplate.GetTypes().at(0) == ModifierType::Invalid)
{
continue;
}
modifiers.push_back(modifierTemplate);
}
}
if (json.contains("levels"))
{
auto levels = json["levels"];
if (levels.contains("min"))
{
minLevel = levels["min"].get<int32_t>();
const auto rasio = 1; //45.0f / 85.0f;
minLevel = std::max(1, static_cast<int32_t>(minLevel * rasio));
}
else
{
minLevel = 1;
}
if (levels.contains("max"))
{
maxLevel = levels["max"].get<int32_t>();
}
else
{
maxLevel = 45;
}
}
if (json.contains("rarity"))
{
rarity = magic_enum::enum_cast<ModifierRarity>(json["rarity"].get<std::string>()).value_or(ModifierRarity::Common);
}
else
{
rarity = ModifierRarity::Common;
}
}
nlohmann::json nejlika::ModifierNameTemplate::ToJson() const {
nlohmann::json json;
json["name"] = name;
json["type"] = magic_enum::enum_name(type);
if (lot != 0)
{
json["lot"] = lot;
}
if (!itemTypes.empty())
{
nlohmann::json items;
for (const auto& itemType : itemTypes)
{
items.push_back(magic_enum::enum_name(itemType));
}
json["items"] = items;
}
if (!modifiers.empty())
{
nlohmann::json modifierTemplates;
for (const auto& modifier : modifiers)
{
modifierTemplates.push_back(modifier.ToJson());
}
json["modifiers"] = modifierTemplates;
}
nlohmann::json levels;
levels["min"] = minLevel;
levels["max"] = maxLevel;
json["levels"] = levels;
json["rarity"] = magic_enum::enum_name(rarity);
return json;
}
std::vector<ModifierInstance> nejlika::ModifierNameTemplate::GenerateModifiers(int32_t level) const
{
std::vector<ModifierInstance> result;
for (const auto& modifierTemplate : modifiers)
{
auto modifiers = modifierTemplate.GenerateModifiers(level);
result.insert(result.end(), modifiers.begin(), modifiers.end());
}
return result;
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "eItemType.h"
#include "ModifierNameType.h"
#include "ModifierRarity.h"
#include "ModifierTemplate.h"
#include "json.hpp"
namespace nejlika
{
class ModifierNameTemplate
{
public:
ModifierNameTemplate(const nlohmann::json& json);
nlohmann::json ToJson() const;
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
// Getters
const std::string& GetName() const { return name; }
ModifierNameType GetType() const { return type; }
const std::vector<ModifierTemplate>& GetModifiers() const { return modifiers; }
const std::vector<eItemType>& GetItemTypes() const { return itemTypes; }
int32_t GetMinLevel() const { return minLevel; }
int32_t GetMaxLevel() const { return maxLevel; }
ModifierRarity GetRarity() const { return rarity; }
int32_t GetLOT() const { return lot; }
private:
std::string name;
int32_t lot;
ModifierNameType type;
std::vector<ModifierTemplate> modifiers;
std::vector<eItemType> itemTypes;
int32_t minLevel;
int32_t maxLevel;
ModifierRarity rarity;
};
}

16
dGame/ModifierNameType.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
namespace nejlika
{
enum class ModifierNameType : uint8_t
{
Prefix,
Suffix,
Object,
Skill
};
}

14
dGame/ModifierOperator.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
namespace nejlika
{
enum class ModifierOperator : uint8_t
{
Additive,
Multiplicative
};
}

31
dGame/ModifierRarity.cpp Normal file
View File

@@ -0,0 +1,31 @@
#include "ModifierRarity.h"
#include <unordered_map>
using namespace nejlika;
namespace
{
const std::unordered_map<ModifierRarity, std::string> colorMap = {
{ModifierRarity::Common, "#FFFFFF"},
{ModifierRarity::Uncommon, "#B5AC15"},
{ModifierRarity::Rare, "#3EEA4A"},
{ModifierRarity::Epic, "#2F83C1"},
{ModifierRarity::Legendary, "#852DCA"},
{ModifierRarity::Relic, "#00FFFF"}
};
}
const std::string& nejlika::ModifierRarityHelper::GetModifierRarityColor(ModifierRarity rarity)
{
const auto color = colorMap.find(rarity);
if (color != colorMap.end()) {
return color->second;
}
static const std::string white = "#FFFFFF";
return white;
}

24
dGame/ModifierRarity.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
#include <string>
namespace nejlika
{
enum class ModifierRarity : uint8_t
{
Common,
Uncommon,
Rare,
Epic,
Legendary,
Relic
};
namespace ModifierRarityHelper
{
const std::string& GetModifierRarityColor(ModifierRarity rarity);
}
}

40
dGame/ModifierScale.cpp Normal file
View File

@@ -0,0 +1,40 @@
#include "ModifierScale.h"
nejlika::ModifierScale::ModifierScale(const nlohmann::json & json)
{
level = json["level"].get<int32_t>();
const auto rasio = 1; //45.0f / 85.0f;
level = std::max(1, static_cast<int32_t>(level * rasio));
if (json.contains("min")) {
min = json["min"].get<float>();
}
else {
min = 0.0f;
}
if (json.contains("max")) {
max = json["max"].get<float>();
}
else {
max = 0.0f;
}
if (json.contains("value")) {
min = json["value"].get<float>();
max = json["value"].get<float>();
}
}
nlohmann::json nejlika::ModifierScale::ToJson() const
{
nlohmann::json json;
json["level"] = level;
json["min"] = min;
json["max"] = max;
return json;
}

33
dGame/ModifierScale.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
#include "json.hpp"
namespace nejlika
{
class ModifierScale
{
public:
ModifierScale() = default;
ModifierScale(int32_t level, float min, float max) : level(level), min(min), max(max) {}
ModifierScale(const nlohmann::json& json);
nlohmann::json ToJson() const;
int32_t GetLevel() const { return level; }
float GetMin() const { return min; }
float GetMax() const { return max; }
private:
int32_t level = 0;
float min = 0.0f;
float max = 0.0f;
};
}

501
dGame/ModifierTemplate.cpp Normal file
View File

@@ -0,0 +1,501 @@
#include "ModifierTemplate.h"
#include <magic_enum.hpp>
#include <random>
#include <algorithm>
#include <sstream>
#include <iostream>
using namespace nejlika;
nejlika::ModifierTemplate::ModifierTemplate(const nlohmann::json& config) {
if (config.contains("type"))
{
selector = ModifierTemplateSelector::One;
const auto type = magic_enum::enum_cast<ModifierType>(config["type"].get<std::string>());
if (type.has_value())
{
types = {type.value()};
}
else
{
types = {};
}
}
else if (config.contains("all"))
{
selector = ModifierTemplateSelector::All;
types = {};
for (const auto& type : config["all"])
{
const auto t = magic_enum::enum_cast<ModifierType>(type.get<std::string>());
if (t.has_value())
{
types.push_back(t.value());
}
}
}
else if (config.contains("two-of"))
{
selector = ModifierTemplateSelector::Two;
types = {};
for (const auto& type : config["two-of"])
{
const auto t = magic_enum::enum_cast<ModifierType>(type.get<std::string>());
if (t.has_value())
{
types.push_back(t.value());
}
}
}
else
{
types = {};
}
if (config.contains("convert-to"))
{
convertTo = magic_enum::enum_cast<ModifierType>(config["convert-to"].get<std::string>()).value_or(ModifierType::Invalid);
}
else
{
convertTo = ModifierType::Invalid;
}
if (config.contains("scaling"))
{
const auto scaling = config["scaling"];
for (const auto& scaler : scaling)
{
scales.push_back(ModifierScale(scaler));
}
}
if (config.contains("polynomial"))
{
const auto polynomialConfig = config["polynomial"];
for (const auto& term : polynomialConfig)
{
polynomial.push_back(term.get<float>());
}
}
if (config.contains("category"))
{
category = magic_enum::enum_cast<ModifierCategory>(config["category"].get<std::string>()).value_or(ModifierCategory::Player);
}
else
{
category = ModifierCategory::Player;
}
isResistance = config.contains("resistance") ? config["resistance"].get<bool>() : false;
effectID = config.contains("effect-id") ? config["effect-id"].get<uint32_t>() : 0;
effectType = config.contains("effect-type") ? config["effect-type"].get<std::string>() : "";
if (config.contains("operator"))
{
operatorType = magic_enum::enum_cast<ModifierOperator>(config["operator"].get<std::string>()).value_or(ModifierOperator::Additive);
}
else
{
operatorType = ModifierOperator::Additive;
}
// Old format
if (config.contains("percentage"))
{
if (config["percentage"].get<bool>()) {
operatorType = ModifierOperator::Multiplicative;
}
}
if (config.contains("pet"))
{
if (config["pet"].get<bool>()) {
category = ModifierCategory::Pet;
}
}
if (config.contains("owner"))
{
if (config["owner"].get<bool>()) {
category = ModifierCategory::Player;
}
}
if (config.contains("skill"))
{
upgradeName = config["skill"].get<std::string>();
}
}
nlohmann::json nejlika::ModifierTemplate::ToJson() const {
nlohmann::json config;
if (selector == ModifierTemplateSelector::One)
{
config["type"] = magic_enum::enum_name(types[0]);
}
else if (selector == ModifierTemplateSelector::All)
{
config["all"] = true;
for (const auto& type : types)
{
config["types"].push_back(magic_enum::enum_name(type));
}
}
else if (selector == ModifierTemplateSelector::Two)
{
config["two-of"] = true;
for (const auto& type : types)
{
config["two-of"].push_back(magic_enum::enum_name(type));
}
}
if (!scales.empty())
{
nlohmann::json scaling;
for (const auto& scale : scales)
{
scaling.push_back(scale.ToJson());
}
config["scaling"] = scaling;
}
if (!polynomial.empty())
{
nlohmann::json polynomialConfig;
for (const auto& term : polynomial)
{
polynomialConfig.push_back(term);
}
config["polynomial"] = polynomialConfig;
}
config["convert-to"] = magic_enum::enum_name(convertTo);
config["category"] = magic_enum::enum_name(category);
config["resistance"] = isResistance;
config["effect-id"] = effectID;
config["effect-type"] = effectType;
config["operator"] = magic_enum::enum_name(operatorType);
if (!upgradeName.empty())
{
config["skill"] = upgradeName;
}
return config;
}
std::vector<ModifierInstance> nejlika::ModifierTemplate::GenerateModifiers(int32_t level) const {
std::vector<ModifierInstance> modifiers;
std::vector<ModifierType> selectedTypes;
if (types.empty())
{
return modifiers;
}
if (selector == ModifierTemplateSelector::One)
{
selectedTypes = {types[0]};
}
else if (selector == ModifierTemplateSelector::All)
{
selectedTypes = types;
}
else if (selector == ModifierTemplateSelector::Two)
{
if (types.size() < 2)
{
selectedTypes = types;
}
else
{
// Randomly select two types
selectedTypes = types;
std::shuffle(selectedTypes.begin(), selectedTypes.end(), std::mt19937(std::random_device()()));
selectedTypes.resize(2);
}
}
for (const auto& selectedType : selectedTypes)
{
auto modifierOpt = GenerateModifier(selectedType, level);
if (modifierOpt.has_value())
{
modifiers.push_back(modifierOpt.value());
}
}
return modifiers;
}
std::string nejlika::ModifierTemplate::GenerateHtmlString(const std::vector<ModifierTemplate>& modifiers, int32_t level) {
std::stringstream ss;
// target -> resistance -> op -> type -> (min, max)
std::unordered_map<ModifierCategory, std::unordered_map<bool, std::unordered_map<ModifierOperator, std::unordered_map<ModifierType, std::pair<float, float>>>>> modifierMap;
bool hasConvertTo = false;
bool hasSkillModifier = false;
for (const auto& modifier : modifiers) {
if (modifier.GetConvertTo() != ModifierType::Invalid)
{
hasConvertTo = true;
continue;
}
if (!modifier.GetUpgradeName().empty())
{
hasSkillModifier = true;
continue;
}
for (const auto& type : modifier.types) {
if (type == ModifierType::Invalid || type == ModifierType::SkillModifier) {
continue;
}
if (!modifier.polynomial.empty())
{
float value = 0.0f;
int32_t power = 0;
for (const auto& term : modifier.polynomial)
{
value += term * std::pow(level, power);
power++;
}
modifierMap[modifier.category][modifier.isResistance][modifier.operatorType][type] = {value, value};
continue;
}
ModifierScale scale;
bool found = false;
// Select the scale with the highest level that is less than or equal to the current level
for (const auto& s : modifier.scales) {
if (s.GetLevel() <= level && s.GetLevel() > scale.GetLevel()) {
scale = s;
found = true;
}
}
if (!found) {
continue;
}
modifierMap[modifier.category][modifier.isResistance][modifier.operatorType][type] = {scale.GetMin(), scale.GetMax()};
}
}
// Resistances and addatives are not separated, pet and player are
// Summarize the resistances and addatives
for (const auto& target : modifierMap) {
if (target.first == ModifierCategory::Pet) {
ss << "\n<font color=\"#D0AB62\">Pets:</font>\n";
}
for (const auto& resistance : target.second) {
for (const auto& math : resistance.second) {
for (const auto& modifier : math.second) {
ss << "<font color=\"#FFFFFF\">";
ss << ((modifier.second.first > 0) ? (math.first == ModifierOperator::Multiplicative ? "+" : "") : "-");
ss << std::fixed << std::setprecision(1) << std::abs(modifier.second.first);
if (modifier.second.first != modifier.second.second)
{
ss << "/";
ss << std::fixed << std::setprecision(1) << std::abs(modifier.second.second);
}
if (math.first == ModifierOperator::Multiplicative) {
ss << "%";
}
ss << "</font> <font color=\"#D0AB62\">";
ss << " " << nejlika::GetModifierTypeName(modifier.first);
if (resistance.first) {
// If the ss now ends with 'Damage' remove it
if (ss.str().substr(ss.str().size() - 6) == "Damage") {
ss.seekp(-6, std::ios_base::end);
}
ss << " " << "Resistance";
}
ss << "</font>\n";
}
}
}
}
if (hasSkillModifier)
{
for (const auto& modifier : modifiers) {
for (const auto& type : modifier.types) {
if (type != ModifierType::SkillModifier) {
continue;
}
const auto& scalors = modifier.GetScales();
if (scalors.empty())
{
continue;
}
const auto& m = scalors[0];
ss << "<font color=\"" << GetModifierTypeColor(type) << "\">";
ss << ((m.GetMin() > 0) ? "+" : "-");
ss << std::fixed << std::setprecision(0) << std::abs(m.GetMin());
ss << " to ";
ss << modifier.GetUpgradeName();
ss << "</font>\n";
}
}
}
if (hasConvertTo)
{
for (const auto& modifier : modifiers) {
if (modifier.GetConvertTo() == ModifierType::Invalid)
{
continue;
}
for (const auto& type : modifier.types) {
if (type == ModifierType::Invalid) {
continue;
}
const auto& scalors = modifier.GetScales();
auto m = scalors[0];
for (const auto& s : scalors) {
if (s.GetLevel() <= level && s.GetLevel() > m.GetLevel()) {
m = s;
}
}
ss << "<font color=\"#FFFFFF\">";
// +xx/yy% of T1 converted to T2
ss << ((m.GetMin() > 0) ? "" : "-");
ss << std::fixed << std::setprecision(0) << std::abs(m.GetMin());
if (m.GetMin() != m.GetMax())
{
ss << "/";
ss << std::fixed << std::setprecision(0) << std::abs(m.GetMax());
}
ss << "%</font> <font color=\"#D0AB62\">";
ss << " of ";
ss << nejlika::GetModifierTypeName(type);
ss << " converted to ";
ss << nejlika::GetModifierTypeName(modifier.GetConvertTo());
ss << "</font>\n";
}
}
}
return ss.str();
}
std::optional<ModifierInstance> nejlika::ModifierTemplate::GenerateModifier(ModifierType type, int32_t level) const {
ModifierScale scale;
bool found = false;
if (!polynomial.empty())
{
float value = 0.0f;
int32_t power = 0;
for (const auto& term : polynomial)
{
value += term * std::pow(level, power);
power++;
}
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo, upgradeName);
}
// Select the scale with the highest level that is less than or equal to the current level
for (const auto& s : scales) {
if ((s.GetLevel() <= level) && (s.GetLevel() > scale.GetLevel())) {
std::cout << "Found scale: " << s.GetMin() << " - " << s.GetMax() << " for level " << s.GetLevel() << std::endl;
scale = s;
found = true;
}
}
if (!found) {
return std::nullopt;
}
float value = 0;
if (scale.GetMax() == scale.GetMin())
{
value = scale.GetMin();
}
else
{
value = (GeneralUtils::GenerateRandomNumber<uint32_t>(0, 100) / 100.0f) * (scale.GetMax() - scale.GetMin()) + scale.GetMin();
}
std::cout << "Generated modifier: " << value << " with level " << level << " for type: " << magic_enum::enum_name(type) << std::endl;
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo, upgradeName);
}

109
dGame/ModifierTemplate.h Normal file
View File

@@ -0,0 +1,109 @@
#pragma once
#include <cstdint>
#include <vector>
#include <string>
#include <optional>
#include "ModifierInstance.h"
#include "ModifierScale.h"
namespace nejlika
{
enum ModifierTemplateSelector : uint8_t
{
One,
All,
Two
};
class ModifierTemplate
{
public:
ModifierTemplate(
const std::vector<ModifierType>& types, ModifierTemplateSelector selector, ModifierCategory category, bool isResistance, uint32_t effectID, const std::string& effectType
) : types(types), selector(selector), category(category), isResistance(isResistance), effectID(effectID), effectType(effectType) {}
/**
* @brief Construct a new Modifier Template object from a json configuration.
*
* @param config The json configuration.
*/
ModifierTemplate(const nlohmann::json& config);
/**
* @brief Convert the modifier template to a json representation.
*
* @return The json representation.
*/
nlohmann::json ToJson() const;
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
// Getters and setters
const std::vector<ModifierType>& GetTypes() const { return types; }
ModifierType GetConvertTo() const { return convertTo; }
ModifierTemplateSelector GetSelector() const { return selector; }
const std::vector<ModifierScale>& GetScales() const { return scales; }
ModifierCategory GetCategory() const { return category; }
bool IsResistance() const { return isResistance; }
uint32_t GetEffectID() const { return effectID; }
std::string GetEffectType() const { return effectType; }
const std::string& GetUpgradeName() const { return upgradeName; }
void SetTypes(const std::vector<ModifierType>& types) { this->types = types; }
void SetConvertTo(ModifierType convertTo) { this->convertTo = convertTo; }
void SetSelector(ModifierTemplateSelector selector) { this->selector = selector; }
void SetScales(const std::vector<ModifierScale>& scales) { this->scales = scales; }
void SetCategory(ModifierCategory category) { this->category = category; }
void SetIsResistance(bool isResistance) { this->isResistance = isResistance; }
void SetEffectID(uint32_t effectID) { this->effectID = effectID; }
void SetEffectType(const std::string& effectType) { this->effectType = effectType; }
void SetUpgradeName(const std::string& upgradeName) { this->upgradeName = upgradeName; }
/**
* @brief Generate a HTML string representation of a set of modifier templates.
*
* @param modifiers The modifier templates to generate the HTML string for.
* @param level The level of the modifier templates.
* @return The HTML string.
*/
static std::string GenerateHtmlString(const std::vector<ModifierTemplate>& modifiers, int32_t level);
private:
std::optional<ModifierInstance> GenerateModifier(ModifierType type, int32_t level) const;
std::vector<ModifierType> types;
ModifierType convertTo;
ModifierTemplateSelector selector;
std::vector<ModifierScale> scales;
std::vector<float> polynomial;
ModifierCategory category;
ModifierOperator operatorType;
bool isResistance;
uint32_t effectID;
std::string effectType;
std::string upgradeName;
};
}

216
dGame/ModifierType.cpp Normal file
View File

@@ -0,0 +1,216 @@
#include "ModifierType.h"
#include <unordered_map>
#include <unordered_set>
using namespace nejlika;
namespace
{
const std::unordered_map<ModifierType, std::string> colorMap = {
{ModifierType::Health, "#750000"},
{ModifierType::Armor, "#525252"},
{ModifierType::Imagination, "#0077FF"},
{ModifierType::Offensive, "#71583B"},
{ModifierType::Defensive, "#71583B"},
{ModifierType::Physical, "#666666"},
{ModifierType::Pierce, "#4f4f4f"},
{ModifierType::Vitality, "#e84646"},
{ModifierType::Fire, "#ff0000"},
{ModifierType::Cold, "#94d0f2"},
{ModifierType::Lightning, "#00a2ff"},
{ModifierType::Corruption, "#3d00ad"},
{ModifierType::Psychic, "#4b0161"},
{ModifierType::Acid, "#00ff00"},
};
const std::unordered_map<ModifierType, std::string> nameMap = {
{ModifierType::Health, "Health"},
{ModifierType::Armor, "Armor"},
{ModifierType::Imagination, "Imagination"},
{ModifierType::Offensive, "Offensive Ability"},
{ModifierType::Defensive, "Defensive Ability"},
{ModifierType::Physical, "Physical Damage"},
{ModifierType::Pierce, "Pierce Damage"},
{ModifierType::Vitality, "Vitality Damage"},
{ModifierType::Fire, "Fire Damage"},
{ModifierType::Cold, "Cold Damage"},
{ModifierType::Lightning, "Lightning Damage"},
{ModifierType::Corruption, "Corruption Damage"},
{ModifierType::Psychic, "Psychic Damage"},
{ModifierType::Acid, "Acid Damage"},
{ModifierType::InternalDisassembly, "Internal Disassembly"},
{ModifierType::InternalDisassemblyDuration, "Internal Disassembly Duration"},
{ModifierType::Burn, "Burn"},
{ModifierType::BurnDuration, "Burn Duration"},
{ModifierType::Frostburn, "Frostburn"},
{ModifierType::FrostburnDuration, "Frostburn Duration"},
{ModifierType::Poison, "Poison"},
{ModifierType::PoisonDuration, "Poison Duration"},
{ModifierType::Electrocute, "Electrocute"},
{ModifierType::ElectrocuteDuration, "Electrocute Duration"},
{ModifierType::VitalityDecay, "Vitality Decay"},
{ModifierType::VitalityDecayDuration, "Vitality Decay Duration"},
{ModifierType::Seperation, "Seperation"},
{ModifierType::SeperationDuration, "Seperation Duration"},
{ModifierType::Elemental, "Elemental Damage"},
{ModifierType::Damage, "Damage"},
{ModifierType::Speed, "Speed"},
{ModifierType::AttackSpeed, "Attack Speed"},
{ModifierType::BlockRecovery, "Block Recovery Speed"},
{ModifierType::BlockChance, "Chance to Block"},
{ModifierType::Block, "Damage to Block"},
{ModifierType::CriticalDamage, "Critical-hit Damage"},
{ModifierType::HealthDrain, "Damage converted to Health"},
{ModifierType::ArmorPiercing, "Armor Piercing"},
{ModifierType::ReducedStunDuration, "Reduced Stun Duration"},
{ModifierType::SkillCooldownReduction, "Skill Cooldown Reduction"},
{ModifierType::SkillRecharge, "Skill Recharge Time"},
{ModifierType::Slow, "Slow"},
{ModifierType::Physique, "Physique"},
{ModifierType::Cunning, "Cunning"},
{ModifierType::Spirit, "Imagination"},
{ModifierType::AttacksPerSecond, "Attacks per Second"},
{ModifierType::ImaginationCost, "Imagination Cost"},
{ModifierType::MainWeaponDamage, "Main Weapon Damage"},
{ModifierType::Stun, "Target Stun Duration"}
};
const std::unordered_map<ModifierType, ModifierType> resistanceMap = {
{ModifierType::Physical, ModifierType::Physical},
{ModifierType::Pierce, ModifierType::Pierce},
{ModifierType::Vitality, ModifierType::Vitality},
{ModifierType::Fire, ModifierType::Fire},
{ModifierType::Cold, ModifierType::Cold},
{ModifierType::Lightning, ModifierType::Lightning},
{ModifierType::Corruption, ModifierType::Corruption},
{ModifierType::Psychic, ModifierType::Psychic},
{ModifierType::Acid, ModifierType::Acid},
{ModifierType::InternalDisassembly, ModifierType::Physical},
{ModifierType::Burn, ModifierType::Fire},
{ModifierType::Frostburn, ModifierType::Cold},
{ModifierType::Poison, ModifierType::Acid},
{ModifierType::VitalityDecay, ModifierType::Vitality},
{ModifierType::Electrocute, ModifierType::Lightning},
{ModifierType::Seperation, ModifierType::Seperation}
};
const std::unordered_set<ModifierType> normalDamageTypes = {
ModifierType::Physical,
ModifierType::Pierce,
ModifierType::Vitality,
ModifierType::Fire,
ModifierType::Cold,
ModifierType::Lightning,
ModifierType::Corruption,
ModifierType::Psychic,
ModifierType::Acid
};
const std::unordered_map<ModifierType, ModifierType> durationMap = {
{ModifierType::InternalDisassembly, ModifierType::InternalDisassemblyDuration},
{ModifierType::Burn, ModifierType::BurnDuration},
{ModifierType::Frostburn, ModifierType::FrostburnDuration},
{ModifierType::Poison, ModifierType::PoisonDuration},
{ModifierType::VitalityDecay, ModifierType::VitalityDecayDuration},
{ModifierType::Electrocute, ModifierType::ElectrocuteDuration},
{ModifierType::Seperation, ModifierType::SeperationDuration}
};
const std::unordered_map<ModifierType, ModifierType> overTimeMap = {
{ModifierType::Physical, ModifierType::InternalDisassembly},
{ModifierType::Fire, ModifierType::Burn},
{ModifierType::Cold, ModifierType::Frostburn},
{ModifierType::Poison, ModifierType::Poison},
{ModifierType::Vitality, ModifierType::VitalityDecay},
{ModifierType::Lightning, ModifierType::Electrocute}
};
const std::unordered_set<ModifierType> isOverTimeMap = {
ModifierType::InternalDisassembly,
ModifierType::Burn,
ModifierType::Frostburn,
ModifierType::Poison,
ModifierType::VitalityDecay,
ModifierType::Electrocute,
ModifierType::Seperation
};
const std::unordered_set<ModifierType> isDurationType = {
ModifierType::InternalDisassemblyDuration,
ModifierType::BurnDuration,
ModifierType::FrostburnDuration,
ModifierType::PoisonDuration,
ModifierType::VitalityDecayDuration,
ModifierType::ElectrocuteDuration,
ModifierType::SeperationDuration
};
}
const std::string& nejlika::GetModifierTypeColor(ModifierType type)
{
const auto color = colorMap.find(type);
if (color != colorMap.end()) {
return color->second;
}
static const std::string white = "#FFFFFF";
return white;
}
const std::string& nejlika::GetModifierTypeName(ModifierType type) {
const auto name = nameMap.find(type);
if (name != nameMap.end()) {
return name->second;
}
static const std::string invalid = "Invalid";
return invalid;
}
const ModifierType nejlika::GetResistanceType(ModifierType type) {
const auto resistance = resistanceMap.find(type);
if (resistance != resistanceMap.end()) {
return resistance->second;
}
return ModifierType::Invalid;
}
const bool nejlika::IsNormalDamageType(ModifierType type) {
return normalDamageTypes.find(type) != normalDamageTypes.end();
}
const ModifierType nejlika::GetOverTimeType(ModifierType type) {
const auto overTime = overTimeMap.find(type);
if (overTime != overTimeMap.end()) {
return overTime->second;
}
return ModifierType::Invalid;
}
const ModifierType nejlika::GetDurationType(ModifierType type) {
const auto duration = durationMap.find(type);
if (duration != durationMap.end()) {
return duration->second;
}
return ModifierType::Invalid;
}
const bool nejlika::IsOverTimeType(ModifierType type) {
return isOverTimeMap.find(type) != isOverTimeMap.end();
}
const bool nejlika::IsDurationType(ModifierType type) {
return isDurationType.find(type) != isDurationType.end();
}

96
dGame/ModifierType.h Normal file
View File

@@ -0,0 +1,96 @@
#pragma once
#include <cstdint>
#include <string>
namespace nejlika
{
enum class ModifierType : uint8_t
{
Health,
Armor,
Imagination,
Physique,
Cunning,
Spirit,
Offensive,
Defensive,
// Normal Types
Physical,
Fire,
Cold,
Lightning,
Acid,
Vitality,
Pierce,
Corruption, // Aether
Psychic, // Chaos
// Duration Types
InternalDisassembly, // Internal Trauma
InternalDisassemblyDuration,
Burn,
BurnDuration,
Frostburn,
FrostburnDuration,
Poison,
PoisonDuration,
VitalityDecay,
VitalityDecayDuration,
Electrocute,
ElectrocuteDuration,
Seperation, // Bleeding
SeperationDuration,
// Special
Elemental, // Even split between Fire, Cold, Lightning
Damage,
Speed,
AttackSpeed,
SkillModifier,
Slow,
ArmorPiercing,
ReducedStunDuration,
SkillCooldownReduction,
SkillRecharge,
BlockRecovery,
BlockChance,
Block,
HealthRegen,
ImaginationRegen,
AttacksPerSecond,
ImaginationCost,
MainWeaponDamage,
Stun,
CriticalDamage,
HealthDrain,
Invalid
};
const std::string& GetModifierTypeColor(ModifierType type);
const std::string& GetModifierTypeName(ModifierType type);
const ModifierType GetResistanceType(ModifierType type);
const bool IsNormalDamageType(ModifierType type);
const bool IsOverTimeType(ModifierType type);
const bool IsDurationType(ModifierType type);
const ModifierType GetOverTimeType(ModifierType type);
const ModifierType GetDurationType(ModifierType type);
}

215
dGame/NejlikaData.cpp Normal file
View File

@@ -0,0 +1,215 @@
#include "NejlikaData.h"
#include "Game.h"
#include "dConfig.h"
#include "json.hpp"
#include "Logger.h"
#include "Lookup.h"
#include <fstream>
using namespace nejlika;
namespace
{
std::unordered_map<nejlika::ModifierNameType, std::vector<nejlika::ModifierNameTemplate>> modifierNameTemplates;
std::unordered_map<LWOOBJID, nejlika::AdditionalItemData> additionalItemData;
std::unordered_map<LWOOBJID, nejlika::AdditionalEntityData> additionalEntityData;
std::unordered_map<LOT, nejlika::EntityTemplate> entityTemplates;
std::unordered_map<LOT, nejlika::UpgradeTemplate> upgradeTemplates;
nejlika::Lookup lookup;
}
const nejlika::Lookup& nejlika::NejlikaData::GetLookup()
{
return lookup;
}
const std::unordered_map<ModifierNameType,std::vector<ModifierNameTemplate>>& nejlika::NejlikaData::GetModifierNameTemplates()
{
return modifierNameTemplates;
}
const std::vector<nejlika::ModifierNameTemplate>& nejlika::NejlikaData::GetModifierNameTemplates(ModifierNameType type)
{
const auto it = modifierNameTemplates.find(type);
if (it != modifierNameTemplates.end()) {
return it->second;
}
static const std::vector<nejlika::ModifierNameTemplate> empty;
return empty;
}
const std::optional<AdditionalItemData*> nejlika::NejlikaData::GetAdditionalItemData(LWOOBJID id) {
const auto& it = additionalItemData.find(id);
if (it != additionalItemData.end()) {
return std::optional<AdditionalItemData*>(&it->second);
}
return std::nullopt;
}
const std::optional<AdditionalEntityData*> nejlika::NejlikaData::GetAdditionalEntityData(LWOOBJID id) {
const auto& it = additionalEntityData.find(id);
if (it != additionalEntityData.end()) {
return std::optional<AdditionalEntityData*>(&it->second);
}
return std::nullopt;
}
const std::optional<EntityTemplate*> nejlika::NejlikaData::GetEntityTemplate(LOT lot) {
const auto& it = entityTemplates.find(lot);
if (it != entityTemplates.end()) {
return std::optional<EntityTemplate*>(&it->second);
}
return std::nullopt;
}
const std::optional<UpgradeTemplate*> nejlika::NejlikaData::GetUpgradeTemplate(LOT lot) {
const auto& it = upgradeTemplates.find(lot);
if (it != upgradeTemplates.end()) {
return std::optional<UpgradeTemplate*>(&it->second);
}
return std::nullopt;
}
void nejlika::NejlikaData::SetAdditionalItemData(LWOOBJID id, AdditionalItemData data) {
additionalItemData[id] = data;
}
void nejlika::NejlikaData::SetAdditionalEntityData(LWOOBJID id, AdditionalEntityData data) {
additionalEntityData[id] = data;
}
void nejlika::NejlikaData::UnsetAdditionalItemData(LWOOBJID id) {
additionalItemData.erase(id);
}
void nejlika::NejlikaData::UnsetAdditionalEntityData(LWOOBJID id) {
additionalEntityData.erase(id);
}
void nejlika::NejlikaData::LoadNejlikaData()
{
const auto& lookupFile = Game::config->GetValue("lookup");
if (!lookupFile.empty())
{
lookup = Lookup(lookupFile);
}
modifierNameTemplates.clear();
// Load data from json file
const auto& directory_name = Game::config->GetValue("nejlika");
if (directory_name.empty())
{
return;
}
// Loop through all files in the directory
for (const auto& entry : std::filesystem::directory_iterator(directory_name))
{
if (!entry.is_regular_file())
{
continue;
}
// It has to end on .mod.json
const auto& path = entry.path().string();
if (path.size() < 9 || path.substr(path.size() - 9) != ".mod.json")
{
continue;
}
LoadNejlikaDataFile(entry.path().string());
}
}
void nejlika::NejlikaData::LoadNejlikaDataFile(const std::string& path) {
std::ifstream file(path);
if (!file.is_open())
{
LOG("Failed to open nejlika data file: %s", path.c_str());
return;
}
nlohmann::json json;
try
{
json = nlohmann::json::parse(file);
}
catch (const nlohmann::json::exception& e)
{
LOG("Failed to parse nejlika data file: %s", e.what());
return;
}
if (json.contains("modifier-templates"))
{
const auto& modifierTemplates = json["modifier-templates"];
for (const auto& value : modifierTemplates)
{
auto modifierTemplate = ModifierNameTemplate(value);
if (modifierTemplate.GetModifiers().empty())
{
continue;
}
modifierNameTemplates[modifierTemplate.GetType()].push_back(modifierTemplate);
}
}
LOG("Loaded %d modifier templates", modifierNameTemplates.size());
if (json.contains("entity-templates"))
{
const auto& entityTemplatesArray = json["entity-templates"];
for (const auto& value : entityTemplatesArray)
{
auto entityTemplate = EntityTemplate(value);
entityTemplates[entityTemplate.GetLOT()] = entityTemplate;
}
}
LOG("Loaded %d entity templates", entityTemplates.size());
if (json.contains("upgrade-templates"))
{
const auto& upgradeTemplatesArray = json["upgrade-templates"];
for (const auto& value : upgradeTemplatesArray)
{
auto upgradeTemplate = UpgradeTemplate(value);
upgradeTemplates[upgradeTemplate.GetLot()] = upgradeTemplate;
}
}
LOG("Loaded %d upgrade templates", upgradeTemplates.size());
}

42
dGame/NejlikaData.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
#include <vector>
#include "ModifierNameTemplate.h"
#include "EntityTemplate.h"
#include "AdditionalItemData.h"
#include "AdditionalEntityData.h"
#include "UpgradeTemplate.h"
#include "Lookup.h"
namespace nejlika::NejlikaData
{
const std::unordered_map<ModifierNameType, std::vector<ModifierNameTemplate>>& GetModifierNameTemplates();
const std::vector<ModifierNameTemplate>& GetModifierNameTemplates(ModifierNameType type);
const std::optional<AdditionalItemData*> GetAdditionalItemData(LWOOBJID id);
const std::optional<AdditionalEntityData*> GetAdditionalEntityData(LWOOBJID id);
const std::optional<EntityTemplate*> GetEntityTemplate(LOT lot);
const std::optional<UpgradeTemplate*> GetUpgradeTemplate(LOT lot);
void SetAdditionalItemData(LWOOBJID id, AdditionalItemData data);
void SetAdditionalEntityData(LWOOBJID id, AdditionalEntityData data);
void UnsetAdditionalItemData(LWOOBJID id);
void UnsetAdditionalEntityData(LWOOBJID id);
void LoadNejlikaData();
void LoadNejlikaDataFile(const std::string& path);
const Lookup& GetLookup();
}

33
dGame/NejlikaHelpers.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include "NejlikaHelpers.h"
void nejlika::NejlikaHelpers::RenderDamageText(const std::string & text, Entity * attacker, Entity * damaged,
float scale, int32_t fontSize, int32_t colorR, int32_t colorG, int32_t colorB, int32_t colorA)
{
if (damaged == nullptr) {
return;
}
auto damagedPosition = damaged->GetPosition();
// Add a slight random offset to the damage position
damagedPosition.x += (rand() % 10 - 5) / 5.0f;
damagedPosition.y += (rand() % 10 - 5) / 5.0f;
damagedPosition.z += (rand() % 10 - 5) / 5.0f;
const auto& damageText = text;
std::stringstream damageUIMessage;
damageUIMessage << 0.0825 << ";" << 0.12 << ";" << damagedPosition.x << ";" << damagedPosition.y + 4.5f << ";" << damagedPosition.z << ";" << 0.1 << ";";
damageUIMessage << 200 * scale << ";" << 200 * scale << ";" << 0.5 << ";" << 1.0 << ";" << damageText << ";" << 4 << ";" << fontSize << ";" << colorR << ";" << colorG << ";" << colorB << ";";
damageUIMessage << colorA;
const auto damageUIStr = damageUIMessage.str();
if (damaged->IsPlayer()) {
damaged->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
} else if (attacker != nullptr && attacker->IsPlayer()) {
attacker->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
}
}

9
dGame/NejlikaHelpers.h Normal file
View File

@@ -0,0 +1,9 @@
#include <Entity.h>
namespace nejlika::NejlikaHelpers
{
void RenderDamageText(const std::string& text, Entity* attacker, Entity* damaged,
float scale = 1, int32_t fontSize = 4, int32_t colorR = 255, int32_t colorG = 255, int32_t colorB = 255, int32_t colorA = 0);
}

1048
dGame/NejlikaHooks.cpp Normal file

File diff suppressed because it is too large Load Diff

10
dGame/NejlikaHooks.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
namespace nejlika::NejlikaHooks
{
void InstallHooks();
void ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}

View File

@@ -18,6 +18,8 @@ void PlayerManager::AddPlayer(Entity* player) {
if (iter == m_Players.end()) {
m_Players.push_back(player);
OnPlayerAdded(player);
}
}
@@ -27,6 +29,8 @@ bool PlayerManager::RemovePlayer(Entity* player) {
const bool toReturn = iter != m_Players.end();
if (toReturn) {
m_Players.erase(iter);
OnPlayerRemoved(player);
}
return toReturn;

View File

@@ -2,6 +2,7 @@
#define __PLAYERMANAGER__H__
#include "dCommonVars.h"
#include "Observable.h"
#include <string>
@@ -20,6 +21,9 @@ namespace PlayerManager {
Entity* GetPlayer(LWOOBJID playerID);
const std::vector<Entity*>& GetAllPlayers();
static Observable<Entity*> OnPlayerAdded;
static Observable<Entity*> OnPlayerRemoved;
};
#endif //!__PLAYERMANAGER__H__

View File

@@ -67,7 +67,7 @@ void Trade::SetAccepted(LWOOBJID participant, bool value) {
if (participant == m_ParticipantA) {
m_AcceptedA = !value;
Log::Info("Accepted from A ({}), B: ({})", value, m_AcceptedB);
LOG("Accepted from A (%d), B: (%d)", value, m_AcceptedB);
auto* entityB = GetParticipantBEntity();
@@ -77,7 +77,7 @@ void Trade::SetAccepted(LWOOBJID participant, bool value) {
} else if (participant == m_ParticipantB) {
m_AcceptedB = !value;
Log::Info("Accepted from B ({}), A: ({})", value, m_AcceptedA);
LOG("Accepted from B (%d), A: (%d)", value, m_AcceptedA);
auto* entityA = GetParticipantAEntity();
@@ -125,7 +125,7 @@ void Trade::Complete() {
// First verify both players have the coins and items requested for the trade.
if (characterA->GetCoins() < m_CoinsA || characterB->GetCoins() < m_CoinsB) {
Log::Warn("Possible coin trade cheating attempt! Aborting trade.");
LOG("Possible coin trade cheating attempt! Aborting trade.");
return;
}
@@ -133,11 +133,11 @@ void Trade::Complete() {
auto* itemToRemove = inventoryA->FindItemById(tradeItem.itemId);
if (itemToRemove) {
if (itemToRemove->GetCount() < tradeItem.itemCount) {
Log::Warn("Possible cheating attempt from {:s} in trading!!! Aborting trade", characterA->GetName());
LOG("Possible cheating attempt from %s in trading!!! Aborting trade", characterA->GetName().c_str());
return;
}
} else {
Log::Warn("Possible cheating attempt from {:s} in trading due to item not being available!!!", characterA->GetName());
LOG("Possible cheating attempt from %s in trading due to item not being available!!!", characterA->GetName().c_str());
return;
}
}
@@ -146,11 +146,11 @@ void Trade::Complete() {
auto* itemToRemove = inventoryB->FindItemById(tradeItem.itemId);
if (itemToRemove) {
if (itemToRemove->GetCount() < tradeItem.itemCount) {
Log::Warn("Possible cheating attempt from {:s} in trading!!! Aborting trade", characterB->GetName());
LOG("Possible cheating attempt from %s in trading!!! Aborting trade", characterB->GetName().c_str());
return;
}
} else {
Log::Warn("Possible cheating attempt from {:s} in trading due to item not being available!!! Aborting trade", characterB->GetName());
LOG("Possible cheating attempt from %s in trading due to item not being available!!! Aborting trade", characterB->GetName().c_str());
return;
}
}
@@ -194,7 +194,7 @@ void Trade::SendUpdateToOther(LWOOBJID participant) {
uint64_t coins;
std::vector<TradeItem> itemIds;
Log::Info("Attempting to send trade update");
LOG("Attempting to send trade update");
if (participant == m_ParticipantA) {
other = GetParticipantBEntity();
@@ -228,7 +228,7 @@ void Trade::SendUpdateToOther(LWOOBJID participant) {
items.push_back(tradeItem);
}
Log::Info("Sending trade update");
LOG("Sending trade update");
GameMessages::SendServerTradeUpdate(other->GetObjectID(), coins, items, other->GetSystemAddress());
}
@@ -281,7 +281,7 @@ Trade* TradingManager::NewTrade(LWOOBJID participantA, LWOOBJID participantB) {
trades[tradeId] = trade;
Log::Info("Created new trade between ({}) <-> ({})", participantA, participantB);
LOG("Created new trade between (%llu) <-> (%llu)", participantA, participantB);
return trade;
}

16
dGame/TriggerParameters.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
#include "BehaviorSlot.h"
namespace nejlika
{
class TriggerParameters
{
public:
int32_t SkillID = 0;
BehaviorSlot SelectedBehaviorSlot = BehaviorSlot::Invalid;
};
}

355
dGame/UpgradeEffect.cpp Normal file
View File

@@ -0,0 +1,355 @@
#include "UpgradeEffect.h"
#include "GeneralUtils.h"
#include "GameMessages.h"
#include "InventoryComponent.h"
#include <magic_enum.hpp>
#include <iostream>
using namespace nejlika;
nejlika::UpgradeEffect::UpgradeEffect(const nlohmann::json& json)
{
Load(json);
}
nlohmann::json nejlika::UpgradeEffect::ToJson() const
{
nlohmann::json json;
json["trigger-type"] = static_cast<int32_t>(triggerType);
nlohmann::json modifiersJson = nlohmann::json::array();
for (const auto& modifier : modifiers) {
modifiersJson.push_back(modifier.ToJson());
}
json["modifiers"] = modifiersJson;
if (!chance.empty()) {
nlohmann::json chanceJson = nlohmann::json::array();
for (const auto& scale : chance) {
chanceJson.push_back({
{"level", scale.level},
{"value", scale.value}
});
}
json["chance"] = chanceJson;
}
if (effectID != 0) {
json["effect-id"] = effectID;
}
if (!effectType.empty()) {
json["effect-type"] = effectType;
}
if (!conditions.empty()) {
nlohmann::json conditionsJson = nlohmann::json::array();
for (const auto& condition : conditions) {
conditionsJson.push_back(magic_enum::enum_name(condition));
}
json["conditions"] = conditionsJson;
}
if (equipSkillID != 0) {
json["grant-skill-id"] = equipSkillID;
}
return json;
}
std::vector<ModifierInstance> nejlika::UpgradeEffect::GenerateModifiers(int32_t level) const
{
std::vector<ModifierInstance> result;
for (const auto& modifier : modifiers) {
auto instances = modifier.GenerateModifiers(level);
result.insert(result.end(), instances.begin(), instances.end());
}
return result;
}
void nejlika::UpgradeEffect::Load(const nlohmann::json& json)
{
triggerType = magic_enum::enum_cast<UpgradeTriggerType>(json["trigger-type"].get<std::string>()).value_or(UpgradeTriggerType::OnHit);
modifiers.clear();
if (json.contains("modifiers")){
for (const auto& modifier : json["modifiers"]) {
ModifierTemplate effect(modifier);
modifiers.push_back(effect);
}
}
if (json.contains("chance")) {
chance.clear();
for (const auto& scale : json["chance"]) {
chance.push_back({
scale["level"].get<int32_t>(),
scale["value"].get<float>()
});
}
}
if (json.contains("effect-id")) {
effectID = json["effect-id"].get<int32_t>();
}
if (json.contains("effect-type")) {
effectType = json["effect-type"].get<std::string>();
}
if (json.contains("conditions")) {
conditions.clear();
for (const auto& condition : json["conditions"]) {
conditions.push_back(magic_enum::enum_cast<UpgradeTriggerCondition>(condition.get<std::string>()).value_or(UpgradeTriggerCondition::None));
}
}
if (json.contains("grant-skill-id")) {
equipSkillID = json["grant-skill-id"].get<int32_t>();
}
}
float nejlika::UpgradeEffect::CalculateChance(int32_t level) const {
if (chance.empty()) {
return 1;
}
// Find the highest level that is less than or equal to the given level
float value = 0;
for (const auto& scale : chance) {
if (scale.level <= level) {
value = scale.value;
}
}
return value;
}
bool nejlika::UpgradeEffect::CheckConditions(LWOOBJID origin, const TriggerParameters& params) const {
auto* entity = Game::entityManager->GetEntity(origin);
if (!entity) {
return false;
}
auto* inventory = entity->GetComponent<InventoryComponent>();
if (!inventory) {
return false;
}
const auto& skills = inventory->GetSkills();
const auto& equipped = inventory->GetEquippedItems();
for (const auto& condition : conditions) {
switch (condition) {
case UpgradeTriggerCondition::PrimaryAbility:
if (params.SelectedBehaviorSlot != BehaviorSlot::Primary) {
return false;
}
break;
case UpgradeTriggerCondition::UseSkill:
if (params.SkillID != equipSkillID) {
return false;
}
case UpgradeTriggerCondition::None:
break;
case UpgradeTriggerCondition::Unarmed:
if (equipped.contains("special_r")) {
return false;
}
break;
case UpgradeTriggerCondition::Melee:
if (!equipped.contains("special_r")) {
return false;
}
break;
case UpgradeTriggerCondition::TwoHanded:
{
if (!equipped.contains("special_r")) {
return false;
}
const auto& weaponLot = equipped.at("special_r").lot;
const auto& info = Inventory::FindItemComponent(weaponLot);
if (!info.isTwoHanded) {
return false;
}
break;
}
case UpgradeTriggerCondition::Shield:
if (!equipped.contains("special_l")) {
return false;
}
break;
default:
break;
}
}
return true;
}
void nejlika::UpgradeEffect::OnTrigger(LWOOBJID origin) const {
auto* entity = Game::entityManager->GetEntity(origin);
if (!entity) {
return;
}
auto* inventory = entity->GetComponent<InventoryComponent>();
if (!inventory) {
return;
}
}
std::vector<ModifierInstance> nejlika::UpgradeEffect::Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params) {
std::vector<ModifierInstance> result;
for (const auto& modifier : modifiers) {
if (modifier.GetTriggerType() != triggerType) {
continue;
}
if (!modifier.CheckConditions(origin, params)) {
continue;
}
float chanceRoll = GeneralUtils::GenerateRandomNumber<float>(0, 1);
if (chanceRoll > modifier.CalculateChance(level)) {
continue;
}
std::cout << "Triggering effect trigger type: " << magic_enum::enum_name(triggerType) << std::endl;
modifier.OnTrigger(origin);
auto instances = modifier.GenerateModifiers(level);
result.insert(result.end(), instances.begin(), instances.end());
if (modifier.effectID == 0) {
continue;
}
GameMessages::SendPlayFXEffect(
origin,
modifier.effectID,
GeneralUtils::UTF8ToUTF16(modifier.effectType),
std::to_string(GeneralUtils::GenerateRandomNumber<size_t>())
);
}
return result;
}
void nejlika::UpgradeEffect::AddSkill(LWOOBJID origin) const {
auto* entity = Game::entityManager->GetEntity(origin);
if (!entity) {
return;
}
auto* inventory = entity->GetComponent<InventoryComponent>();
if (!inventory) {
return;
}
if (triggerType != UpgradeTriggerType::Active) {
return;
}
if (equipSkillID != 0) {
inventory->AddSkill(equipSkillID);
}
}
void nejlika::UpgradeEffect::RemoveSkill(LWOOBJID origin) const {
auto* entity = Game::entityManager->GetEntity(origin);
if (!entity) {
return;
}
auto* inventory = entity->GetComponent<InventoryComponent>();
if (!inventory) {
return;
}
if (triggerType != UpgradeTriggerType::Active) {
return;
}
if (equipSkillID != 0) {
inventory->RemoveSkill(equipSkillID);
}
}
std::string nejlika::UpgradeEffect::GenerateHtmlString(const std::vector<UpgradeEffect>& effects, int32_t level) {
std::stringstream ss;
for (const auto& effect : effects) {
const auto chance = effect.CalculateChance(level);
for (const auto& condition : effect.conditions) {
if (condition == UpgradeTriggerCondition::None || condition == UpgradeTriggerCondition::UseSkill) {
continue;
}
ss << "<font color=\"#D0AB62\">";
switch (condition) {
case UpgradeTriggerCondition::PrimaryAbility:
ss << "On main-hand attack";
break;
case UpgradeTriggerCondition::Melee:
ss << "Requires melee weapon";
break;
case UpgradeTriggerCondition::TwoHanded:
ss << "Requires two-handed weapon";
break;
case UpgradeTriggerCondition::Shield:
ss << "Requires a shield";
break;
case UpgradeTriggerCondition::Unarmed:
ss << "Requires unarmed attack";
break;
}
ss << "</font>\n\n";
}
if (chance < 1.0f) {
ss << "<font color=\"#FFFFFF\">" << chance * 100 << "%</font><font color=\"#D0AB62\"> chance to trigger</font>\n";
}
std::cout << "Level " << level << " chance: " << chance << std::endl;
ss << ModifierInstance::GenerateHtmlString(effect.GenerateModifiers(level));
}
return ss.str();
}

69
dGame/UpgradeEffect.h Normal file
View File

@@ -0,0 +1,69 @@
#pragma once
#include "ModifierTemplate.h"
#include "UpgradeTriggerType.h"
#include "UpgradeTriggerCondition.h"
#include <InventoryComponent.h>
#include "TriggerParameters.h"
#include <dCommonVars.h>
namespace nejlika
{
class UpgradeEffect
{
public:
UpgradeEffect(const nlohmann::json& json);
nlohmann::json ToJson() const;
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
void Load(const nlohmann::json& json);
float CalculateChance(int32_t level) const;
bool CheckConditions(LWOOBJID origin, const TriggerParameters& params) const;
void OnTrigger(LWOOBJID origin) const;
static std::vector<ModifierInstance> Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params);
// Getters
const std::vector<ModifierTemplate>& GetModifiers() const { return modifiers; }
UpgradeTriggerType GetTriggerType() const { return triggerType; }
void AddSkill(LWOOBJID origin) const;
void RemoveSkill(LWOOBJID origin) const;
/**
* @brief Generate a HTML string representation of a set of upgrade effects.
*
* @param modifiers The upgrade effects to generate the HTML string for.
* @param level The level of the upgrade effects.
* @return The HTML string.
*/
static std::string GenerateHtmlString(const std::vector<UpgradeEffect>& effects, int32_t level);
private:
struct UpgradeScale
{
int32_t level;
float value;
};
std::vector<UpgradeScale> chance;
std::vector<UpgradeTriggerCondition> conditions;
UpgradeTriggerType triggerType;
int32_t equipSkillID = 0;
std::vector<ModifierTemplate> modifiers;
int32_t effectID = 0;
std::string effectType = "";
std::vector<ModifierTemplate> debuffs;
std::vector<UpgradeScale> debuffDuration;
};
}

90
dGame/UpgradeTemplate.cpp Normal file
View File

@@ -0,0 +1,90 @@
#include "UpgradeTemplate.h"
#include "NejlikaData.h"
using namespace nejlika;
nejlika::UpgradeTemplate::UpgradeTemplate(const nlohmann::json& json)
{
Load(json);
}
nlohmann::json nejlika::UpgradeTemplate::ToJson() const
{
nlohmann::json json;
json["name"] = name;
json["lot"] = lot;
json["max-level"] = maxLevel;
nlohmann::json passivesJson = nlohmann::json::array();
for (const auto& passive : passives) {
passivesJson.push_back(passive.ToJson());
}
json["passives"] = passivesJson;
return json;
}
void nejlika::UpgradeTemplate::Load(const nlohmann::json& json)
{
name = json["name"].get<std::string>();
if (json["lot"].is_string()) {
lot = NejlikaData::GetLookup().GetValue(json["lot"].get<std::string>());
}
else {
lot = json["lot"].get<int32_t>();
}
maxLevel = json.contains("max-level") ? json["max-level"].get<int32_t>() : 1;
passives.clear();
if (json.contains("modifiers")) {
for (const auto& modifier : json["modifiers"]) {
ModifierTemplate modTemplate(modifier);
modifiers.push_back(modTemplate);
}
}
if (json.contains("passives")) {
for (const auto& passive : json["passives"]) {
UpgradeEffect effect(passive);
passives.push_back(effect);
}
}
}
std::vector<ModifierInstance> nejlika::UpgradeTemplate::Trigger(int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params) const {
level = std::min(level, maxLevel);
return UpgradeEffect::Trigger(passives, level, triggerType, origin, params);
}
std::vector<ModifierInstance> nejlika::UpgradeTemplate::GenerateModifiers(int32_t level) const {
level = std::min(level, maxLevel);
std::vector<ModifierInstance> result;
for (const auto& modifier : modifiers) {
auto instances = modifier.GenerateModifiers(level);
result.insert(result.end(), instances.begin(), instances.end());
}
return result;
}
void nejlika::UpgradeTemplate::AddSkills(LWOOBJID origin) const {
for (const auto& passive : passives) {
passive.AddSkill(origin);
}
}
void nejlika::UpgradeTemplate::RemoveSkills(LWOOBJID origin) const {
for (const auto& passive : passives) {
passive.RemoveSkill(origin);
}
}

48
dGame/UpgradeTemplate.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "json.hpp"
#include "UpgradeEffect.h"
#include "TriggerParameters.h"
namespace nejlika
{
class UpgradeTemplate
{
public:
UpgradeTemplate() = default;
UpgradeTemplate(const nlohmann::json& json);
nlohmann::json ToJson() const;
void Load(const nlohmann::json& json);
const std::string& GetName() const { return name; }
int32_t GetLot() const { return lot; }
int32_t GetMaxLevel() const { return maxLevel; }
const std::vector<UpgradeEffect>& GetPassives() const { return passives; }
const std::vector<ModifierTemplate>& GetModifiers() const { return modifiers; }
std::vector<ModifierInstance> Trigger(int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params) const;
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
void AddSkills(LWOOBJID origin) const;
void RemoveSkills(LWOOBJID origin) const;
private:
std::string name = "";
int32_t lot = 0;
int32_t maxLevel = 0;
std::vector<UpgradeEffect> passives;
std::vector<ModifierTemplate> modifiers;
};
}

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