1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-07-27 04:14:07 +00:00

Compare commits

...

135 commits

Author SHA1 Message Date
David Cernat
ad9ee80641
Merge pull request #532 from nalal/patch-1
[Server] Added functionality for TES3MP server to catch system signals
2019-11-19 20:31:28 +02:00
David Cernat
57f84914c3 [Client] Prevent permanent deletion of player markers on cell changes 2019-07-14 22:42:55 +03:00
David Cernat
48ebdb38c7 [Client] Set LocalPlayer's killer for damage over time spell effects
Simplify related code that sets LocalActors' killers for damage over time spell effects.
2019-07-06 14:44:04 +03:00
nalal
7316c8aafd
Added functionality for TES3MP server to catch system signals
This should permit the TES3MP server to catch when a "SIGTERM"(Standard program stop) or "SIGINT"(Ctrl+C) is sent to the process and allow that to initiate the standard shutdown procedure by halting the networking loop peacefully.
2019-07-06 04:03:50 -07:00
David Cernat
94a9292cc6 [Client] Use correct index when unassigning quickkeys from WindowManager 2019-07-01 13:48:45 +03:00
David Cernat
77952440f6 [Client] Clean up handling of draw states 2019-06-19 08:24:53 +03:00
David Cernat
301fff7fe5 [Client] Always allow spellcasting from dedicated players and actors 2019-06-17 04:21:43 +03:00
David Cernat
01804af100 [Client] Add updateInventoryWindow() method to LocalPlayer 2019-06-09 01:00:01 +03:00
David Cernat
49fa35a516 [Client] Use ObjectActivate packets when picking up items from inventory 2019-06-09 01:00:01 +03:00
Andrei Kortunov
792fbfe2e1 Fix type in the preprocessor directive 2019-05-30 09:19:33 +04:00
David Cernat
f29bfb6b8e [Client] Add debug for received kill counts 2019-05-22 23:20:44 +03:00
David Cernat
4d40df3ea8 [Server] Don't automatically send kill count packets to other players
This should be handled in server scripts instead so that servers with respawning NPCs can be have kills shared across parties and witnesses instead of shared across all players on the server.
2019-05-22 22:13:42 +03:00
David Cernat
abc4090a0f [Client] Fix loop in ActiveSpells::getEffectDuration() 2019-05-20 23:09:54 +03:00
David Cernat
e84f85863e
Merge pull request #527 from quexten/0.7.0
Update LuaBridge to e3f7a022
2019-05-12 16:23:59 +03:00
Bernd Schoolmann
1f2349ef6e Update LuaBridge to e3f7a022 2019-05-12 03:14:03 +02:00
David Cernat
023ead937f [Server] Fix stack overflows by preventing infinite growth in Lua stack 2019-05-12 01:58:18 +03:00
David Cernat
6322ae081d [Server] Add extern to folders used to search for include files 2019-05-12 01:54:28 +03:00
David Cernat
3805edaf78 [Server] Don't crash when adding a requiredDataFile w/ no checksum twice 2019-05-05 17:39:49 +03:00
David Cernat
02e646e838 [Client] Use check for credits file that makes more sense 2019-04-25 04:39:25 +03:00
David Cernat
437854180c [Client] Clean up variable & function names in mwmp/Main.cpp 2019-04-25 04:29:54 +03:00
David Cernat
f6db7d4fc5 [Client] Avoid sending repeated ObjectDelete packets about an object 2019-04-24 00:52:11 +03:00
David Cernat
7523a73346 [Server] Fix return values for server functions providing system info 2019-04-23 23:40:41 +03:00
David Cernat
d8919dcec6 [Server] Refer to data files instead of plugins in var & function names 2019-04-23 22:45:25 +03:00
David Cernat
a2f110ae6d
Merge pull request #524 from uramer/0.7.0summon_duration
Make the server receive the correct summon durations regardless of spell casting order
2019-04-21 21:59:09 +03:00
uramer
2531378d0b make getEffectDuration to look for a specific effect 2019-04-21 20:53:38 +02:00
David Cernat
8aad93b904
Merge pull request #519 from TES3MP/0.7.0-alpha
[General] Update positions for dead players on other clients
2019-03-24 03:54:43 +02:00
David Cernat
3effd5f1ff [General] Update positions for dead players on other clients
Dead players will now show up at the correct cell and position for living players, making server scripts that allow players to revive each other much more functional.
2019-03-24 03:52:05 +02:00
David Cernat
4692f29b9d
Merge pull request #517 from uramer/0.7.0markers
update player map markers when client changes cell
2019-03-22 21:39:06 +02:00
David Cernat
03d377ec54
Merge pull request #518 from TES3MP/0.7.0-alpha
[General] Rename CellReplace packet into CellReset
2019-03-22 21:36:07 +02:00
David Cernat
8ff2d1b829 [General] Rename CellReplace packet into CellReset 2019-03-22 21:33:34 +02:00
David Cernat
cb82318c36 [General] Fix problems with Utils::getArchitectureType() 2019-03-22 03:09:11 +02:00
uramer
3b2098382b update player map markers when client changes cell 2019-03-21 16:27:15 +01:00
David Cernat
cb5e24e6c5
Merge pull request #516 from TES3MP/0.7.0-alpha
[Server] Add GetMillisecondsSinceServerStart() server function
2019-03-20 20:05:30 +02:00
David Cernat
91f82d845c [Server] Add GetMillisecondsSinceServerStart() server function 2019-03-20 20:02:31 +02:00
David Cernat
d35026bbf5
Merge pull request #515 from TES3MP/0.7.0-alpha
0.7.0 alpha
2019-03-20 18:58:31 +02:00
David Cernat
bd677726bf [Server] Add StatsFunctions that get/set damage to attributes/skills 2019-03-20 18:54:35 +02:00
David Cernat
9fc4c83858 [Client] Send skill/attribute packets when skills/attributes are damaged 2019-03-20 18:40:46 +02:00
David Cernat
ece39748de [Server] Fix typo causing recursion in deprecated actor list function 2019-03-20 17:01:21 +02:00
David Cernat
5c4d3df551 [Server] Deprecate DoesFileExist(), add DoesFilePathExist() 2019-03-19 04:52:58 +02:00
David Cernat
2cdabddc0e [Server] Move most MiscellaneousFunctions to ServerFunctions 2019-03-19 04:25:33 +02:00
David Cernat
b46767de6e [Server] Clean up recent additions to ServerFunctions 2019-03-19 03:57:16 +02:00
David Cernat
911079e0bc
Merge pull request #512 from TES3MP/0.7.0-alpha
0.7.0 alpha
2019-03-12 05:38:36 +02:00
David Cernat
331fa86844 [Server] Call OnServerPostInit after OnRequestDataFileList
This allows different actions to be taken in OnServerPostInit based on what the data files being used are.
2019-03-12 05:36:33 +02:00
David Cernat
a0ec9dfd2e [Server] Rename OnRequestPluginList into OnRequestDataFileList 2019-03-12 03:18:57 +02:00
David Cernat
986528c67d [Server] Add error message as argument to OnServerScriptCrash 2019-03-12 02:15:20 +02:00
David Cernat
552a94a0ca [Server] Add OnServerScriptCrash script event 2019-03-10 00:46:40 +02:00
David Cernat
a508a0faf8 [Server] Turn GetArguments() from ScriptFunctions into Utils function 2019-02-24 01:43:04 +02:00
David Cernat
dcbc9d1831 [Client] Print cells for actor deaths 2019-02-21 21:51:02 +02:00
David Cernat
828c52138f [Documentation] Update readme and credits
According to some legal advice I've received, the "TES3MP Team" is too ambiguous of a legal entity, so – with Koncord's agreement – the copyright is now assigned specifically to us, the project's developers.
2019-02-19 17:29:29 +02:00
David Cernat
69e7d3f2a7 [Documentation] Update credits 2019-02-14 13:07:54 +02:00
David Cernat
f3b8a5b909 [General] Check integrity of credits only on Windows clients
This avoids the problems that were encountered in Linux and macOS builds regarding this check while also still addressing the scenario where official Windows builds had their credits modified by people unrelated to the project.
2019-02-14 00:29:55 +02:00
David Cernat
a0ad0b29bc Merge branch '0.7.0' of https://github.com/TES3MP/openmw-tes3mp into 0.7.0 2019-02-13 21:57:24 +02:00
David Cernat
222837976c [Server] Fix type name warning for Player
The warning in Visual Studio was: "'Player': type name first seen using 'class' now seen using 'struct'"
2019-02-13 21:56:47 +02:00
Koncord
77386525f2 [General] Update Travis CI 2019-02-14 01:56:10 +08:00
David Cernat
c058dce346 [General] Clarify meaning of commit hash displayed on start 2019-01-31 13:39:06 +02:00
David Cernat
1df1515c7e [Client] Add logging for invalid enchantmentIds in RecordHelper 2019-01-23 01:04:59 +02:00
David Cernat
999ce857c7 [Client] Add logging for records ignored due to their invalid baseIds 2019-01-23 00:48:06 +02:00
David Cernat
db7e09f441 [Client] Use more consistent logging when reading dynamic record packets 2019-01-23 00:38:05 +02:00
David Cernat
0fa116b47d [Client] Remove useless lines in RecordHelper 2019-01-23 00:20:51 +02:00
Koncord
0df32accca [Server] Fix ARM build 2019-01-21 12:02:02 +08:00
David Cernat
fd40e8c971 [Client] Prevent ObjectState spam by not resending an already sent state 2019-01-15 14:26:00 +02:00
David Cernat
6e47b65205 [Client] Set attribute increases & level progress after correct packets
Originally, the PlayerSkill packet contained skills, attribute increases and level progress. In 78441c769a, the attribute increases were moved to the PlayerAttribute packet and the level progress was moved to the PlayerLevel packet, but – due to an oversight – attribute increases and level progress were still being applied to the local player only when a PlayerSkill packet was received, based on whatever values were stored from the last PlayerAttribute and PlayerLevel packets.
2019-01-11 14:26:13 +02:00
David Cernat
f481c85e07 [Client] Use ADD before REMOVE for PlayerInventory in repair/recharge
Previously, when recharging or repairing an item, the client sent a PlayerInventory packet to the server with the old version of the item that was supposed to be removed and then it sent a PlayerInventory packet with the new version of the item that was supposed to be added.

Unfortunately, the current CoreScripts make it so custom items using generated IDs have their records deleted when they are completely removed from the world, however briefly, even if they are added back immediately afterwards. In practice, this meant that – before this commit – recharging or repairing a custom item led to its removal from the player inventory stored on the server, followed by the deletion of its record, followed by its readdition to the inventory (but with the record staying deleted). Logging out and logging back in immediately prevented the player from receiving the item anymore because of its now non-existent record.
2019-01-11 13:08:26 +02:00
David Cernat
8a99f215f6 [Client] Add LocalPlayer::sendItemChange() variant with mwmp::Item arg 2019-01-11 12:54:47 +02:00
David Cernat
db9c1b9882 [Client] Add MechanicsHelper::getItem() for getting mwmp::Item from Ptr 2019-01-11 12:53:26 +02:00
David Cernat
799241e8c6 [Client] Use informative error message for RefData::setCount() issue 2019-01-11 08:16:29 +02:00
David Cernat
43f195f0c7 [Client] Use clearer debug for actor initializations 2019-01-05 23:27:35 +02:00
David Cernat
2e1d4a9449 [Server] Fix non-Windows builds 2019-01-05 22:11:58 +02:00
David Cernat
81e2e48561 [Client] Fix item magic casting synchronization for spell scrolls
Previously, spell scrolls were used up before their IDs could be included in attacks packets supposed to be sent for them.
2018-12-31 13:24:32 +02:00
David Cernat
d83160523f [Client] Add items required for item magic casting when they are missing 2018-12-31 06:55:35 +02:00
David Cernat
433a69a588 [Client] Send all data for newly initialized LocalActors at least once 2018-12-31 04:36:59 +02:00
David Cernat
e70fd2cf3a [Server] Accept clients with wrong password on servers with no password 2018-12-31 03:52:25 +02:00
David Cernat
eb52babf29 [Server] Print IP instead of name or PID for players unable to connect
The player name was always blank in such situations, providing no useful information. The PID was not useful in any way either.
2018-12-30 18:02:26 +02:00
David Cernat
e96091fd6b [General] Use more consistent variable names for password, address, etc. 2018-12-30 17:23:12 +02:00
David Cernat
906d2a837d [Client] Send PlayerInventory packets when recharging items w/ soulgems 2018-12-30 11:58:33 +02:00
David Cernat
71679934a1 [Client] Send PlayerInventory packets when repairing items 2018-12-30 09:39:46 +02:00
David Cernat
5d9893ee92 [Client] Set actor killer correctly for spells that do damage over time
Additionally, clean up comments related to other code that sets actor killers.
2018-12-30 07:40:11 +02:00
David Cernat
6e1504f0a1 [Server] Use clearer variable & function names in TimerAPI 2018-12-30 04:15:53 +02:00
David Cernat
42b5a8054f [Server] Remove unusable position functions for players 2018-12-30 03:17:37 +02:00
Koncord
4ce0331f1b [Server] Fix GCC build 2018-12-29 15:54:08 +08:00
Koncord
9343b8af2f [Server] Remove unused function 2018-12-29 14:02:08 +08:00
Koncord
c2230a8a21 [Server] Add MP flag to the server if enabled 2018-12-29 12:03:01 +08:00
Koncord
a0e89208a0 [General] Fix standalone server build 2018-12-29 11:58:49 +08:00
Koncord
55cea491ca [Server] Introduce MS VC++ 2017 support 2018-12-29 11:57:26 +08:00
Koncord
6af2400752 [Server] Remove usages of get/set env. Add GetModDir function 2018-12-29 11:40:31 +08:00
Koncord
b3456a8841 [Server] Fix invalidation of iterators 2018-12-29 11:10:20 +08:00
David Cernat
343dd8b5ea [Client] Fix addition of items to player inventories
Previously, multiple stacks of the same item ID could overwrite data in each other because of how the logic in ContainerStore::add() works. For example, a stack of 5 grand soul gems with no souls would get added to the player, then the attempt to add a grand soul gem with a particular soul would retrieve the previous stack first before setting all of it to that soul, resulting in 6 grand soul gems with that soul.
2018-12-26 13:41:19 +02:00
David Cernat
76ac905efc [Client] Send PlayerInventory packets when trapping souls in soulgems 2018-12-26 12:25:00 +02:00
David Cernat
f853368641 [Client] Fix loss of player items in ContainerStore::unstack()
Previously, unstacking items for a player led to a PlayerInventory packet being sent about the items' removal.

This change makes it so both a packet about their re-addition and their removal are sent instead, cancelling each other out, which is inelegant, but arguably preferable to complicating the sending of PlayerInventory packets again.
2018-12-26 12:24:26 +02:00
David Cernat
5e38e8abdb [Server] Add GetArchitectureType() script function
Additionally, bring GetOperatingSystem() up-to-date by making it use the renamed function in Utils.
2018-12-17 11:55:50 +02:00
David Cernat
9fe54aa8c6 [General] Add getArchitectureType() to multiplayer Utils
Additionally, rename getOperatingSystem() into getOperatingSystemType() for clarity.
2018-12-17 11:46:51 +02:00
David Cernat
fa1700e2ab [Server] Add GetOperatingSystemType() script function 2018-12-17 11:32:31 +02:00
David Cernat
da6b89c185 [General] Add getOperatingSystem() to multiplayer Utils 2018-12-17 10:47:34 +02:00
David Cernat
50714599d9 [Client] Spawn at exterior 0, -7 by default 2018-12-17 08:25:22 +02:00
David Cernat
afd17e5a48 [Client] Don't finish drag & drop that is supposed to be unsuccessful
This prevents items from vanishing when your attempt to drop them in a full container is denied.
2018-12-06 18:11:52 +02:00
David Cernat
a6c6db89fc [Client] Send object packets when scripts use PlaceItem/PlaceItemCell 2018-12-05 01:56:27 +02:00
David Cernat
d05a82a734 [Client] Avoid repetitive code when unequipping items in resurrection 2018-12-04 03:55:03 +02:00
David Cernat
b5b26c6685
Merge pull request #492 from terabyte25/patch-7
[Client] Disallow opening inventory menu when not logged in
2018-12-01 23:17:05 +02:00
terrabyte25
35755eb1f1
[Client] Disallow opening inventory menu when not logged in 2018-12-01 10:51:33 -06:00
David Cernat
b7090b2550 [Server] Add experimental option for not crashing from Lua script errors
Additionally, fix return type of GetPluginEnforcementState()
2018-12-01 03:03:39 +02:00
David Cernat
b39e3f518b [Client] Use correct log levels for inventory and dynamic record packets 2018-11-30 23:38:16 +02:00
David Cernat
d8ca268067 [Server] Move plugin enforcement functions to ServerFunctions 2018-11-30 22:43:10 +02:00
David Cernat
2933526995 [Server] Include errors related to Lua calls in server logs 2018-11-30 22:01:02 +02:00
David Cernat
ef80894c5c
Merge pull request #486 from testman42/patch-2
Use more descriptive terminology for chat modes
2018-11-17 23:19:17 +02:00
Testman
6b3f598837
Use more descriptive terminology for chat modes
Changed "Chat disabled" to "Chat hidden", "Chat enabled" to "Chat visible" and "Chat in hidden mode" to "Chat appearing when needed".
2018-11-17 16:54:14 +01:00
David Cernat
eb3ae95f0e [Documentation] Display build status for correct branch 2018-11-16 06:40:21 +02:00
David Cernat
d3eb106c3b [Documentation] Update readme for current situation 2018-11-16 04:49:14 +02:00
David Cernat
e834a4ec74 [Client] Find closest enchantmentCharge in getItemPtrFromStore()
Enchanted inventory items continuously recharge their enchantment charges, which getItemPtrFromStore() should account for.

Additionally, prevent framelistener errors caused by PlayerItemUse packets about non-existent items.
2018-11-13 20:36:31 +02:00
David Cernat
bc7bcae190
Merge pull request #484 from GrimKriegor/0.7.0
[General] Change the default plugins home location
2018-11-12 07:11:37 +02:00
Grim Kriegor
8f90f8a3b8 [General] Change the default plugins home location 2018-11-12 01:10:10 +00:00
Koncord
e162af0003 [Server] Disallow non void callbacks 2018-10-30 15:32:52 +08:00
Koncord
07a5f5296c [Server] Rework OnRequestPluginList callback. Add AddPluginHash function 2018-10-30 14:56:54 +08:00
Koncord
20a7619a4a [Server] Remove result from the OnPlayerConnect callback
Now it's recommended to use tes3mp.Kick() function
2018-10-30 13:18:32 +08:00
Koncord
f1e8569291 [Server] Remove result from the OnPlayerSendMessage callback 2018-10-30 13:13:07 +08:00
Koncord
efa362031e [Server] Remove unused Main callback 2018-10-30 13:03:37 +08:00
Koncord
b83e4056a8 [Server] Remove CallFF dependency as it not fully supported by Windows and MacOS 2018-10-30 12:59:47 +08:00
Koncord
585557ad8a [Server] Remove argument cast in the Call with va_args 2018-10-30 12:58:15 +08:00
Koncord
3101de5f02 [Server] Add kicked load status 2018-10-30 12:56:50 +08:00
Koncord
e5e13b21ae [Client] Fix crash on drag&drop 2018-10-28 16:56:04 +08:00
David Cernat
c65d6c1328 [Client] Disable mListener methods in mwworld/containerstore
This should put an end to frequent crashes until I can fix the problem properly.
2018-10-27 02:19:45 +03:00
David Cernat
1baf82db32 [Client] Avoid PlayerSpellbook packet spam in some mods 2018-10-26 19:07:35 +03:00
David Cernat
d9bc1abf48 [Client] Don't send ObjectScale packets if not logged in 2018-10-26 02:33:47 +03:00
David Cernat
a8cf1e02c4 [Client] Allow unilateral scripted container changes not from console
This prevents infinite loops in certain client scripts from mods that use while loops to determine that all items of a certain type have been removed from a container, such as in the script BCSwap2Arg from  Better_Clothes.
2018-10-25 22:38:45 +03:00
David Cernat
99f8ef88a5 [Server] Add SetObjectActivatingPid() script function 2018-10-22 16:25:50 +03:00
David Cernat
17f13872aa [Client] Use forceUpdate correctly in LocalPlayer::updateStatsDynamic()
Previously, the forceUpdate argument was useless, preventing dynamic stats from being sent by certain newly created characters.
2018-10-22 13:22:23 +03:00
David Cernat
bfd7c83c4d [Client] Fix backwards logic when setting type for AI attacks 2018-10-16 21:18:41 +03:00
David Cernat
d9dd7073cf [General] Send certain packets only when logged in
Previously, client mods adding packet-sending scripts to the spawn area made clients send the associated packets as soon as they inputted their character name when joining a server using those mods. This made the clients either get disconnected for not replying to a handshake first, or it made them get kicked for sending object packets that are disallowed for players who are not logged in.

To fix this, LocalPlayer's hasFinishedCharGen() has been replaced with isLoggedIn(), because the former was already returning true when players inputted their names.
2018-10-13 15:36:13 +03:00
David Cernat
66d666d60c [Client] Use less confusing terminology when displaying plugin mismatch 2018-10-13 14:40:49 +03:00
David Cernat
bb834748c5 [Server] Log player kicks 2018-10-13 09:34:29 +03:00
David Cernat
a3111fbcc1 [Server] Use clearer error message when failing to bind port 2018-10-13 08:35:07 +03:00
David Cernat
3d40a162bc
Merge pull request #476 from SamHellawell/0.7.0-fix-equipcrash-linux-cleanup
Cleanup fix for equip item crash on Linux
2018-10-11 22:52:24 +03:00
Sam Hellawell
df1667b6e4 Cleanup fix for equip item crash on Linux
Signed-off-by: Sam Hellawell <sshellawell@gmail.com>
2018-10-11 21:49:24 +01:00
David Cernat
db2b3e95b8
Merge pull request #475 from SamHellawell/0.7.0-fix-equipcrash-linux
Fix crash when equipping item on linux
2018-10-11 22:40:09 +03:00
Sam Hellawell
1e171ad9fd Fix crash when equipping item on linux
Signed-off-by: Sam Hellawell <sshellawell@gmail.com>
2018-10-11 20:13:22 +01:00
David Cernat
e402a17757 [Client] Don't cast non-weapons to weapons in isUsingRangedWeapon()
This makes lockpicks and probes work again.
2018-10-09 09:54:13 +03:00
130 changed files with 3291 additions and 4076 deletions

View file

@ -4,7 +4,7 @@ os:
osx_image: xcode9.4
language: cpp
sudo: required
dist: trusty
dist: xenial
branches:
only:
- master
@ -15,18 +15,18 @@ env:
global:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: NZmvVuA0O9NJXVQ12tXQZHDJC2mbFgYNFcsicw0DgW1It2Nk5hxIkF0pfu4/Z59mhQuOPgRVjl5b0FKy2Axh0gkWc1DJEXGwNaiW5lpTMNWR1LJG5rxa8LrDUpFkycpbzfAFuTUZu5z3iYVv64XzELvBuqNGhPMu1LeBnrlech0jFNjkR9p5qtJGWb8zYcPMCC57rig8a9g1ABoVYS6UXjrKpx0946ZLRsE5ukc9pXsypGwPmOMyfzZkxxzIqFaxoE5JIEdaJTWba/6Za315ozYYIi/N35ROI1YAv5GHRe/Iw9XAa4vQpbDzjM7ZSsZdTvvQsSU598gD2xC6jFUKSrpW6GZKwM2x236fZLGnOk5Uw7DUbG+AwpcEmxBwoy9PjBl9ZF3tJykI0gROewCy8MODhdsVMKr1HGIMVBIJySm/RnNqtoDbYV8mYnSl5b8rwJiCajoiR8Zuv4CIfGneeH1a3DOQDPH/qkDsU6ilzF4ANsBlMUUpgY653KBMBmTlNuVZSH527tnD7Fg6JgHVuSQkTbRa1vSkR7Zcre604RZcAoaEdbX3bhVDasPPghU/I742L0RH3oQNlR09pPBDZ8kG7ydl4aPHwpCWnvXNM1vgxtGvnYLztwrse7IoaRXRYiMFmrso78WhMWUDKgvY4wV9aeUu0DtnMezZVIQwCKg=
- secure: 1QK0yVyoOB+gf2I7XzvhXu9w/5lq4stBXIwJbVCTjz4Q4XVHCosURaW1MAgKzMrPnbFEwjyn5uQ8BwsvvfkuN1AZD0YXITgc7gyI+J1wQ/p/ljxRxglakU6WEgsTs2J5z9UmGac4YTXg+quK7YP3rv+zuGim2I2rhzImejyzp0Ym3kRCnNcy+SGBsiRaevRJMe00Ch8zGAbEhduQGeSoS6W0rcu02DNlQKiq5NktWsXR+TWWWVfIeIlQR/lbPsCd0pdxMaMv2QCY0rVbwrYxWJwr/Qe45dAdWp+8/C3PbXpeMSGxlLa33nJNX4Lf/djxbjm8KWk6edaXPajrjR/0iwcpwq0jg2Jt6XfEdnJt35F1gpXlc04sxStjG45uloOKCFYT0wdhIO1Lq+hDP54wypQl+JInd5qC001O7pwhVxO36EgKWqo8HD+BqGDBwsNj2engy9Qcp3wO6G0rLBPB3CrZsk9wrHVv5cSiQSLMhId3Xviu3ZI2qEDA+kgTvxrKrsnMj4bILVCyG5Ka2Mj22wIDW9e8oIab9oTdujax3DTN1GkD6QuOAGzwDsNwGASsgfoeZ+FUhgM75RlBWGMilgkmnF7EJ0oAXLEpjtABnEr2d4qHv+y08kOuTDBLB9ExzCIj024dYYYNLZrqPKx0ncHuCMG2QNj2aJAJEZtj1rQ=
addons:
apt:
sources:
- sourceline: 'ppa:openmw/openmw'
- sourceline: 'ppa:rakhimov/boost'
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.8
packages: [
# Dev
cmake, clang-3.8, libunshield-dev, libtinyxml-dev,
g++-6,
cmake, clang-6.0, libunshield-dev, libtinyxml-dev,
g++-8,
# Tests
libgtest-dev, google-mock,
# Boost
@ -45,7 +45,7 @@ addons:
project:
name: "TES3MP/openmw-tes3mp"
description: "<Your project description here>"
notification_email: stas5978@gmail.com
notification_email: koncord@tes3mp.com
build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE -DBUILD_BSATOOL=FALSE -DBUILD_ESMTOOL=FALSE -DBUILD_MWINIIMPORTER=FALSE -DBUILD_LAUNCHER=FALSE"
build_command: "make -j3"
branch_pattern: coverity_scan
@ -53,21 +53,21 @@ matrix:
include:
- os: linux
env:
- ANALYZE="scan-build-3.8 --use-cc clang-3.8 --use-c++ clang++-3.8 "
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
- ANALYZE="scan-build-6.0 --use-cc clang-6.0 --use-c++ clang++-6.0 "
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
compiler: clang
- os: linux
env:
- MATRIX_CC="CC=gcc-6 && CXX=g++-6"
- MATRIX_CC="CC=gcc-8 && CXX=g++-8"
- os: linux
env:
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
allow_failures:
- env:
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
- env:
- ANALYZE="scan-build-3.8 --use-cc clang-3.8 --use-c++ clang++-3.8 "
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
- ANALYZE="scan-build-6.0 --use-cc clang-6.0 --use-c++ clang++-6.0 "
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
before_install:
- ./CI/before_install.${TRAVIS_OS_NAME}.sh

View file

@ -15,19 +15,8 @@ sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so
sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so
cd ~/
git clone https://github.com/TES3MP/RakNet
cd RakNet
cmake . -DRAKNET_ENABLE_DLL=OFF -DRAKNET_ENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=Release
git clone https://github.com/TES3MP/CrabNet
cd CrabNet
cmake . -DCRABNET_ENABLE_DLL=OFF -DCRABNET_ENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=Release
make -j3
cd ~/
git clone https://github.com/Koncord/CallFF
cd CallFF
mkdir build
cd build
cmake ../
make -j3
cd ~/
wget https://github.com/zdevito/terra/releases/download/release-2016-03-25/terra-Linux-x86_64-332a506.zip
unzip terra-Linux-x86_64-332a506.zip

View file

@ -9,8 +9,7 @@ if [ ! -z "${MATRIX_CC}" ]; then
eval "${MATRIX_CC}"
fi
export RAKNET_ROOT=~/RakNet
export Terra_ROOT=~/terra-Linux-x86_64-332a506
export RAKNET_ROOT=~/CrabNet
export CODE_COVERAGE=0
if [ ! -z "${ANALYZE}" ]; then
@ -36,7 +35,5 @@ ${ANALYZE}cmake .. \
-DBINDIR=/usr/games \
-DCMAKE_BUILD_TYPE="None" \
-DUSE_SYSTEM_TINYXML=TRUE \
-DRakNet_LIBRARY_RELEASE=~/RakNet/lib/libRakNetLibStatic.a \
-DRakNet_LIBRARY_DEBUG=~/RakNet/lib/libRakNetLibStatic.a \
-DCallFF_INCLUDES=~/CallFF/include \
-DCallFF_LIBRARY=~/CallFF/build/src/libcallff.a
-DRakNet_LIBRARY_RELEASE=~/CrabNet/lib/libRakNetLibStatic.a \
-DRakNet_LIBRARY_DEBUG=~/CrabNet/lib/libRakNetLibStatic.a

View file

@ -736,7 +736,10 @@ if (WIN32)
endforeach(d)
set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
if (BUILD_OPENMW)
set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
if (BUILD_BSATOOL)
set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")

View file

@ -2,11 +2,11 @@ TES3MP
======
Copyright (c) 2008-2015, OpenMW Team
Copyright (c) 2016-2018, TES3MP Team
Copyright (c) 2016-2019, Stanislav Zhukov & David Cernat
[![Build Status](https://travis-ci.org/TES3MP/openmw-tes3mp.svg?branch=master)](https://travis-ci.org/TES3MP/openmw-tes3mp)
[![Build Status](https://travis-ci.org/TES3MP/openmw-tes3mp.svg?branch=0.7.0)](https://travis-ci.org/TES3MP/openmw-tes3mp)
TES3MP is a project aiming to add multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), an open-source game engine that supports playing "The Elder Scrolls III: Morrowind" by Bethesda Softworks.
TES3MP is a project adding multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), an open-source game engine that supports playing "The Elder Scrolls III: Morrowind" by Bethesda Softworks.
* TES3MP version: 0.7.0-alpha
* OpenMW version: 0.44.0
@ -15,34 +15,35 @@ TES3MP is a project aiming to add multiplayer functionality to [OpenMW](https://
Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVu Font License.txt](https://github.com/TES3MP/openmw-tes3mp/blob/master/files/mygui/DejaVu%20Font%20License.txt) for more information)
Project Status
Project status
--------------
[Version changelog](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-changelog.md)
TES3MP is now playable in most respects. Player and NPC movement, animations, combat and spell casting are properly synchronized with small exceptions, as is picking up and dropping items in the world, using doors and levers, and adding and removing items from containers. Journal entries, faction stats and dialogue topics are also synchronized, allowing the majority of quests to work fine.
As of version 0.7.0, TES3MP is fully playable, providing very extensive player, NPC, world and quest synchronization, as well as state saving and loading, all of which are highly customizable via [serverside Lua scripts](https://github.com/TES3MP/CoreScripts).
[Serverside Lua scripts](https://github.com/TES3MP/CoreScripts) are used to save and load the state of most of the aforementioned.
Contributing
--------------
Development has been relatively fast, but any contribution regarding [code](https://github.com/TES3MP/openmw-tes3mp/blob/master/CONTRIBUTING.md), documentation, bug hunting or video showcases is greatly appreciated.
Test sessions are often advertised on [our Discord server](https://discord.gg/ECJk293) or in [our Steam group](https://steamcommunity.com/groups/mwmulti).
Feel free to contact the [team members](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-credits.md) for any questions you might have.
Getting Started
---------------
* [Quickstart guide](https://github.com/TES3MP/openmw-tes3mp/wiki/Quickstart-guide)
* [Steam group](https://steamcommunity.com/groups/mwmulti) and its [detailed FAQ](https://steamcommunity.com/groups/mwmulti/discussions/1/353916184342480541/)
* [TES3MP section on OpenMW forums](https://forum.openmw.org/viewforum.php?f=44)
* [Subreddit](https://www.reddit.com/r/tes3mp)
* [Known issues and bug reports](https://github.com/TES3MP/openmw-tes3mp/issues)
Remaining gameplay problems mostly relate to AI and the synchronization of clientside script variables.
Donations
---------------
You can benefit the project by contributing to the Patreon pages of our two developers, [Koncord](https://www.patreon.com/Koncord) and [David Cernat](https://www.patreon.com/davidcernat), as well as by supporting [OpenMW](https://openmw.org).
You can benefit the project by donating on Patreon to our two developers, [David Cernat](https://www.patreon.com/davidcernat) and [Koncord](https://www.patreon.com/Koncord), as well as by supporting [OpenMW](https://openmw.org).
Contributing
---------------
Helping us with documentation, bug hunting and video showcases is always greatly appreciated.
For code contributions, it's best to start out with modestly sized fixes and features and work your way up. There are so many different possible implementations of more major features many of which would cause undesirable code or vision conflicts with OpenMW that those should be talked over in advance with the existing developers before effort is spent on them.
Feel free to contact the [team members](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-credits.md) for any questions you might have.
Getting started
---------------
* [Quickstart guide](https://github.com/TES3MP/openmw-tes3mp/wiki/Quickstart-guide)
* [Steam group](https://steamcommunity.com/groups/mwmulti) and its [detailed FAQ](https://steamcommunity.com/groups/mwmulti/discussions/1/353916184342480541/)
* [TES3MP section on OpenMW forums](https://forum.openmw.org/viewforum.php?f=45)
* [Discord server](https://discord.gg/ECJk293)
* [Subreddit](https://www.reddit.com/r/tes3mp)
* [Known issues and bug reports](https://github.com/TES3MP/openmw-tes3mp/issues)

View file

@ -1,12 +1,5 @@
project(tes3mp-server)
if(UNIX) #temporarily disabled for non-unix
if(NOT (${CMAKE_CXX_COMPILER} MATCHES "aarch64" OR ${CMAKE_CXX_COMPILER} MATCHES "arm")) #temporarily disabled for arm
find_package(CallFF REQUIRED)
include_directories(${CallFF_INCLUDES})
endif(NOT (${CMAKE_CXX_COMPILER} MATCHES "aarch64" OR ${CMAKE_CXX_COMPILER} MATCHES "arm"))
endif(UNIX)
option(ENABLE_BREAKPAD "Enable Google Breakpad for Crash reporting" OFF)
if(ENABLE_BREAKPAD)
@ -36,7 +29,7 @@ if(BUILD_WITH_LUA)
Script/LangLua/LangLua.hpp)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DENABLE_LUA")
include_directories(${LuaJit_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/extern/LuaBridge)
include_directories(SYSTEM ${LuaJit_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/extern/LuaBridge)
endif(BUILD_WITH_LUA)
set(NativeScript_Sources
@ -80,7 +73,6 @@ set(SERVER_HEADER
Script/ScriptFunctions.hpp Script/API/TimerAPI.hpp Script/API/PublicFnAPI.hpp
${LuaScript_Headers}
${NativeScript_Headers}
${CallFF_INCLUDES}
)
source_group(tes3mp-server FILES ${SERVER} ${SERVER_HEADER})
@ -152,6 +144,7 @@ set(PROCESSORS
source_group(tes3mp-server\\processors FILES ${PROCESSORS})
include_directories("./")
include_directories(${CMAKE_SOURCE_DIR}/extern)
# Main executable
@ -160,7 +153,22 @@ add_executable(tes3mp-server
${PROCESSORS_ACTOR} ${PROCESSORS_PLAYER} ${PROCESSORS_OBJECT} ${PROCESSORS_WORLDSTATE} ${PROCESSORS}
${APPLE_BUNDLE_RESOURCES}
)
add_definitions(-std=gnu++14 -Wno-ignored-qualifiers)
target_compile_options(tes3mp-server PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/permissive->)
if (OPENMW_MP_BUILD)
target_compile_options(tes3mp-server PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/MP>)
endif()
set_target_properties(tes3mp-server PROPERTIES
CXX_STANDARD 14
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS YES
)
if (UNIX)
target_compile_options(tes3mp-server PRIVATE -Wno-ignored-qualifiers)
endif()
target_link_libraries(tes3mp-server
#${Boost_SYSTEM_LIBRARY}
@ -171,7 +179,6 @@ target_link_libraries(tes3mp-server
components
${LuaJit_LIBRARIES}
${Breakpad_Library}
${CallFF_LIBRARY}
)
if (UNIX)

View file

@ -53,18 +53,21 @@ void Cell::addPlayer(Player *player)
players.push_back(player);
}
void Cell::removePlayer(Player *player)
void Cell::removePlayer(Player *player, bool cleanPlayer)
{
for (Iterator it = begin(); it != end(); it++)
{
if (*it == player)
{
auto it2 = find(player->cells.begin(), player->cells.end(), this);
if (it2 != player->cells.end())
if (cleanPlayer)
{
LOG_APPEND(Log::LOG_INFO, "- Removing %s from Player %s", getDescription().c_str(), player->npc.mName.c_str());
auto it2 = find(player->cells.begin(), player->cells.end(), this);
if (it2 != player->cells.end())
{
LOG_APPEND(Log::LOG_INFO, "- Removing %s from Player %s", getDescription().c_str(), player->npc.mName.c_str());
player->cells.erase(it2);
player->cells.erase(it2);
}
}
LOG_APPEND(Log::LOG_INFO, "- Removing %s from Cell %s", player->npc.mName.c_str(), getDescription().c_str());

View file

@ -28,7 +28,7 @@ public:
Iterator end() const;
void addPlayer(Player *player);
void removePlayer(Player *player);
void removePlayer(Player *player, bool cleanPlayer = true);
void readActorList(unsigned char packetID, const mwmp::BaseActorList *newActorList);
bool containsActor(int refNum, int mpNum);

View file

@ -134,28 +134,35 @@ void CellController::removeCell(Cell *cell)
}
}
void CellController::removePlayer(Cell *cell, Player *player)
void CellController::deletePlayer(Player *player)
{
cell->removePlayer(player);
LOG_APPEND(Log::LOG_INFO, "- Iterating through Cells from Player %s", player->npc.mName.c_str());
if (cell->players.empty())
std::vector<Cell*> toDelete;
auto it = player->getCells()->begin();
const auto endIter = player->getCells()->end();
for (; it != endIter; ++it)
{
Cell *c = *it;
c->removePlayer(player, false);
if (c->players.empty())
toDelete.push_back(c);
}
for (auto &&cell : toDelete)
{
LOG_APPEND(Log::LOG_INFO, "- Cell %s has no players left", cell->getDescription().c_str());
removeCell(cell);
}
}
void CellController::deletePlayer(Player *player)
{
LOG_APPEND(Log::LOG_INFO, "- Iterating through Cells from Player %s", player->npc.mName.c_str());
for (auto it = player->getCells()->begin(); player->getCells()->size() != 0; ++it)
removePlayer(*it, player);
}
void CellController::update(Player *player)
{
for (auto cell : player->cellStateChanges.cellStates)
std::vector<Cell*> toDelete;
for (auto &&cell : player->cellStateChanges.cellStates)
{
if (cell.type == mwmp::CellState::LOAD)
{
@ -171,7 +178,16 @@ void CellController::update(Player *player)
c = getCellByXY(cell.cell.getGridX(), cell.cell.getGridY());
if (c != nullptr)
removePlayer(c, player);
{
c->removePlayer(player);
if (c->players.empty())
toDelete.push_back(c);
}
}
}
for (auto &&cell : toDelete)
{
LOG_APPEND(Log::LOG_INFO, "- Cell %s has no players left", cell->getDescription().c_str());
removeCell(cell);
}
}

View file

@ -30,7 +30,6 @@ public:
Cell * addCell(ESM::Cell cell);
void removeCell(Cell *);
void removePlayer(Cell *cell, Player *player);
void deletePlayer(Player *player);
Cell *getCell(ESM::Cell *esmCell);

View file

@ -14,6 +14,7 @@
#include <Script/API/TimerAPI.hpp>
#include <chrono>
#include <thread>
#include <csignal>
#include "Networking.hpp"
#include "MasterClient.hpp"
@ -30,7 +31,9 @@ using namespace std;
Networking *Networking::sThis = 0;
static int currentMpNum = 0;
static bool pluginEnforcementState = true;
static bool dataFileEnforcementState = true;
static bool scriptErrorIgnoringState = false;
bool killLoop = false;
Networking::Networking(RakNet::RakPeerInterface *peer) : mclient(nullptr)
{
@ -74,9 +77,9 @@ Networking::~Networking()
delete worldstatePacketController;
}
void Networking::setServerPassword(std::string passw) noexcept
void Networking::setServerPassword(std::string password) noexcept
{
serverPassword = passw.empty() ? TES3MP_DEFAULT_PASSW : passw;
serverPassword = password.empty() ? TES3MP_DEFAULT_PASSW : password;
}
bool Networking::isPassworded() const
@ -97,25 +100,32 @@ void Networking::processPlayerPacket(RakNet::Packet *packet)
if (!myPacket->isPacketValid())
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Invalid handshake packet from %d", player->getId());
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Invalid handshake packet from client at %s", packet->systemAddress.ToString());
kickPlayer(player->guid);
return;
}
if (player->isHandshaked())
{
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Wrong handshake with player %d, name: %s", player->getId(),
player->npc.mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Wrong handshake with client at %s", packet->systemAddress.ToString());
kickPlayer(player->guid);
return;
}
if (player->passw != serverPassword)
if (player->serverPassword != serverPassword)
{
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Wrong server password for player %d, name: %s (pass: %s)",
player->getId(), player->npc.mName.c_str(), player->passw.c_str());
kickPlayer(player->guid);
return;
if (isPassworded())
{
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Wrong server password %s used by client at %s",
player->serverPassword.c_str(), packet->systemAddress.ToString());
kickPlayer(player->guid);
return;
}
else
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Client at %s tried to join using password, despite the server not being passworded",
packet->systemAddress.ToString());
}
}
player->setHandshake();
return;
@ -124,9 +134,8 @@ void Networking::processPlayerPacket(RakNet::Packet *packet)
if (!player->isHandshaked())
{
player->incrementHandshakeAttempts();
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Have not completed handshake with player %d", player->getId());
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Attempts so far: %i", player->getHandshakeAttempts());
LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Have not completed handshake with client at %s", packet->systemAddress.ToString());
LOG_APPEND(Log::LOG_WARN, "- Attempts so far: %i", player->getHandshakeAttempts());
if (player->getHandshakeAttempts() > 20)
kickPlayer(player->guid, false);
@ -140,11 +149,10 @@ void Networking::processPlayerPacket(RakNet::Packet *packet)
{
player->setLoadState(Player::LOADED);
static constexpr unsigned int ident = Script::CallbackIdentity("OnPlayerConnect");
Script::CallBackReturn<ident> result = true;
Script::Call<ident>(result, Players::getPlayer(packet->guid)->getId());
unsigned short pid = Players::getPlayer(packet->guid)->getId();
Script::Call<Script::CallbackIdentity("OnPlayerConnect")>(pid);
if (!result)
if (player->getLoadState() == Player::KICKED) // kicked inside in OnPlayerConnect
{
playerPacketController->GetPacket(ID_USER_DISCONNECTED)->setPlayer(Players::getPlayer(packet->guid));
playerPacketController->GetPacket(ID_USER_DISCONNECTED)->Send(false);
@ -222,34 +230,34 @@ bool Networking::preInit(RakNet::Packet *packet, RakNet::BitStream &bsIn)
}
DEBUG_PRINTF("ID_GAME_PREINIT");
PacketPreInit::PluginContainer plugins;
PacketPreInit::PluginContainer dataFiles;
PacketPreInit packetPreInit(peer);
packetPreInit.SetReadStream(&bsIn);
packetPreInit.setChecksums(&plugins);
packetPreInit.setChecksums(&dataFiles);
packetPreInit.Read();
if (!packetPreInit.isPacketValid() || plugins.empty())
if (!packetPreInit.isPacketValid() || dataFiles.empty())
{
LOG_APPEND(Log::LOG_ERROR, "Invalid packetPreInit");
peer->CloseConnection(packet->systemAddress, false); // close connection without notification
return false;
}
auto plugin = plugins.begin();
if (samples.size() == plugins.size())
auto dataFile = dataFiles.begin();
if (samples.size() == dataFiles.size())
{
for (int i = 0; plugin != plugins.end(); plugin++, i++)
for (int i = 0; dataFile != dataFiles.end(); dataFile++, i++)
{
LOG_APPEND(Log::LOG_VERBOSE, "- %X\t%s", plugin->second[0], plugin->first.c_str());
LOG_APPEND(Log::LOG_VERBOSE, "- %X\t%s", dataFile->second[0], dataFile->first.c_str());
// Check if the filenames match, ignoring case
if (Misc::StringUtils::ciEqual(samples[i].first, plugin->first))
if (Misc::StringUtils::ciEqual(samples[i].first, dataFile->first))
{
auto &hashList = samples[i].second;
// Proceed if no checksums have been listed for this plugin on the server
// Proceed if no checksums have been listed for this dataFile on the server
if (hashList.empty())
continue;
auto it = find(hashList.begin(), hashList.end(), plugin->second[0]);
auto it = find(hashList.begin(), hashList.end(), dataFile->second[0]);
// Break the loop if the client's checksum isn't among those accepted by
// the server
if (it == hashList.end())
@ -262,10 +270,10 @@ bool Networking::preInit(RakNet::Packet *packet, RakNet::BitStream &bsIn)
RakNet::BitStream bs;
packetPreInit.SetSendStream(&bs);
// If the loop above was broken, then the client's plugins do not match the server's
if (pluginEnforcementState && plugin != plugins.end())
// If the loop above was broken, then the client's data files do not match the server's
if (dataFileEnforcementState && dataFile != dataFiles.end())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "%s was not allowed to connect due to incompatible plugins", packet->systemAddress.ToString());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "%s was not allowed to connect due to incompatible data files", packet->systemAddress.ToString());
packetPreInit.setChecksums(&samples);
packetPreInit.Send(packet->systemAddress);
peer->CloseConnection(packet->systemAddress, true);
@ -421,14 +429,24 @@ int Networking::incrementMpNum()
return currentMpNum;
}
bool Networking::getPluginEnforcementState()
bool Networking::getDataFileEnforcementState()
{
return pluginEnforcementState;
return dataFileEnforcementState;
}
void Networking::setPluginEnforcementState(bool state)
void Networking::setDataFileEnforcementState(bool state)
{
pluginEnforcementState = state;
dataFileEnforcementState = state;
}
bool Networking::getScriptErrorIgnoringState()
{
return scriptErrorIgnoringState;
}
void Networking::setScriptErrorIgnoringState(bool state)
{
scriptErrorIgnoringState = state;
}
const Networking &Networking::get()
@ -447,44 +465,36 @@ RakNet::SystemAddress Networking::getSystemAddress(RakNet::RakNetGUID guid)
return peer->GetSystemAddressFromGuid(guid);
}
PacketPreInit::PluginContainer Networking::getPluginListSample()
{
PacketPreInit::PluginContainer pls;
unsigned id = 0;
while (true)
{
unsigned field = 0;
auto name = "";
Script::Call<Script::CallbackIdentity("OnRequestPluginList")>(name, id, field++);
if (strlen(name) == 0)
break;
PacketPreInit::HashList hashList;
while (true)
{
auto hash = "";
Script::Call<Script::CallbackIdentity("OnRequestPluginList")>(hash, id, field++);
if (strlen(hash) == 0)
break;
hashList.push_back((unsigned)stoul(hash));
}
pls.push_back({name, hashList});
id++;
}
return pls;
}
void Networking::stopServer(int code)
{
running = false;
exitCode = code;
}
void signalHandler(int signum)
{
cout << "Interrupt signal (" << signum << ") received.\n";
//15 is SIGTERM(Normal OS stop call), 2 is SIGINT(Ctrl+C)
if(signum == 15 or signum == 2)
{
killLoop = true;
}
}
int Networking::mainLoop()
{
RakNet::Packet *packet;
while (running)
struct sigaction sigIntHandler;
sigIntHandler.sa_handler = signalHandler;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
while (running and !killLoop)
{
sigaction(SIGTERM, &sigIntHandler, NULL);
sigaction(SIGINT, &sigIntHandler, NULL);
if (kbhit() && getch() == '\n')
break;
for (packet=peer->Receive(); packet; peer->DeallocatePacket(packet), packet=peer->Receive())
@ -595,16 +605,11 @@ void Networking::InitQuery(std::string queryAddr, unsigned short queryPort)
void Networking::postInit()
{
Script::Call<Script::CallbackIdentity("OnRequestDataFileList")>();
Script::Call<Script::CallbackIdentity("OnServerPostInit")>();
samples = getPluginListSample();
if (mclient)
{
for (auto plugin : samples)
{
if (!plugin.second.empty())
mclient->PushPlugin({plugin.first, plugin.second[0]});
else
mclient->PushPlugin({plugin.first, 0});
}
}
}
PacketPreInit::PluginContainer &Networking::getSamples()
{
return samples;
}

View file

@ -53,8 +53,11 @@ namespace mwmp
void setCurrentMpNum(int value);
int incrementMpNum();
bool getPluginEnforcementState();
void setPluginEnforcementState(bool state);
bool getDataFileEnforcementState();
void setDataFileEnforcementState(bool state);
bool getScriptErrorIgnoringState();
void setScriptErrorIgnoringState(bool state);
MasterClient *getMasterClient();
void InitQuery(std::string queryAddr, unsigned short queryPort);
@ -65,9 +68,10 @@ namespace mwmp
static Networking *getPtr();
void postInit();
PacketPreInit::PluginContainer &getSamples();
private:
bool preInit(RakNet::Packet *packet, RakNet::BitStream &bsIn);
PacketPreInit::PluginContainer getPluginListSample();
std::string serverPassword;
static Networking *sThis;

View file

@ -21,7 +21,6 @@
#include "Cell.hpp"
#include "CellController.hpp"
struct Player;
typedef std::map<RakNet::RakNetGUID, Player*> TPlayers;
typedef std::map<unsigned short, Player*> TSlots;
@ -51,7 +50,8 @@ public:
{
NOTLOADED=0,
LOADED,
POSTLOADED
POSTLOADED,
KICKED
};
Player(RakNet::RakNetGUID guid);

View file

@ -1,7 +1,3 @@
//
// Created by koncord on 15.03.16.
//
#include "TimerAPI.hpp"
#include <chrono>
@ -14,7 +10,7 @@ Timer::Timer(ScriptFunc callback, long msec, const std::string& def, std::vector
{
targetMsec = msec;
this->args = args;
end = true;
isEnded = true;
}
#if defined(ENABLE_LUA)
@ -22,13 +18,13 @@ Timer::Timer(lua_State *lua, ScriptFuncLua callback, long msec, const std::strin
{
targetMsec = msec;
this->args = args;
end = true;
isEnded = true;
}
#endif
void Timer::Tick()
{
if (end)
if (isEnded)
return;
const auto duration = chrono::system_clock::now().time_since_epoch();
@ -36,19 +32,19 @@ void Timer::Tick()
if (time - startTime >= targetMsec)
{
end = true;
isEnded = true;
Call(args);
}
}
bool Timer::IsEnd()
bool Timer::IsEnded()
{
return end;
return isEnded;
}
void Timer::Stop()
{
end = true;
isEnded = true;
}
void Timer::Restart(int msec)
@ -59,7 +55,7 @@ void Timer::Restart(int msec)
void Timer::Start()
{
end = false;
isEnded = false;
const auto duration = chrono::system_clock::now().time_since_epoch();
const auto msec = chrono::duration_cast<chrono::milliseconds>(duration).count();
@ -172,12 +168,12 @@ void TimerAPI::StopTimer(int timerid)
}
}
bool TimerAPI::IsEndTimer(int timerid)
bool TimerAPI::IsTimerElapsed(int timerid)
{
bool ret = false;
try
{
ret = timers.at(timerid)->IsEnd();
ret = timers.at(timerid)->IsEnded();
}
catch(...)
{

View file

@ -1,7 +1,3 @@
//
// Created by koncord on 15.03.16.
//
#ifndef OPENMW_TIMERAPI_HPP
#define OPENMW_TIMERAPI_HPP
@ -27,7 +23,7 @@ namespace mwmp
#endif
void Tick();
bool IsEnd();
bool IsEnded();
void Stop();
void Start();
void Restart(int msec);
@ -36,7 +32,7 @@ namespace mwmp
std::string publ, arg_types;
std::vector<boost::any> args;
Script *scr;
bool end;
bool isEnded;
};
class TimerAPI
@ -50,7 +46,7 @@ namespace mwmp
static void ResetTimer(int timerid, long msec);
static void StartTimer(int timerid);
static void StopTimer(int timerid);
static bool IsEndTimer(int timerid);
static bool IsTimerElapsed(int timerid);
static void Terminate();

View file

@ -537,7 +537,7 @@ void ActorFunctions::InitializeActorList(unsigned short pid) noexcept
void ActorFunctions::CopyLastActorListToStore() noexcept
{
CopyLastActorListToStore();
CopyReceivedActorListToStore();
}
unsigned int ActorFunctions::GetActorRefNumIndex(unsigned int index) noexcept

View file

@ -1,38 +1,12 @@
#include "Miscellaneous.hpp"
#include <components/misc/stringops.hpp>
#include <components/openmw-mp/Log.hpp>
#include <apps/openmw-mp/Script/ScriptFunctions.hpp>
#include <apps/openmw-mp/Networking.hpp>
#include <iostream>
using namespace std;
static std::string tempFilename;
bool MiscellaneousFunctions::DoesFileExist(const char *filePath) noexcept
{
return boost::filesystem::exists(filePath);
}
const char *MiscellaneousFunctions::GetCaseInsensitiveFilename(const char *folderPath, const char *filename) noexcept
{
if (!boost::filesystem::exists(folderPath)) return "invalid";
boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
for (boost::filesystem::directory_iterator itr(folderPath); itr != end_itr; ++itr)
{
if (Misc::StringUtils::ciEqual(itr->path().filename().string(), filename))
{
tempFilename = itr->path().filename().string();
return tempFilename.c_str();
}
}
return "invalid";
}
unsigned int MiscellaneousFunctions::GetLastPlayerId() noexcept
{
return Players::getLastPlayerId();
@ -47,23 +21,3 @@ void MiscellaneousFunctions::SetCurrentMpNum(int mpNum) noexcept
{
mwmp::Networking::getPtr()->setCurrentMpNum(mpNum);
}
int MiscellaneousFunctions::GetPluginEnforcementState() noexcept
{
return mwmp::Networking::getPtr()->getPluginEnforcementState();
}
void MiscellaneousFunctions::SetPluginEnforcementState(bool state) noexcept
{
mwmp::Networking::getPtr()->setPluginEnforcementState(state);
}
void MiscellaneousFunctions::LogMessage(unsigned short level, const char *message) noexcept
{
LOG_MESSAGE_SIMPLE(level, "[Script]: %s", message);
}
void MiscellaneousFunctions::LogAppend(unsigned short level, const char *message) noexcept
{
LOG_APPEND(level, "[Script]: %s", message);
}

View file

@ -4,45 +4,15 @@
#include "../Types.hpp"
#define MISCELLANEOUSAPI \
{"DoesFileExist", MiscellaneousFunctions::DoesFileExist},\
{"GetCaseInsensitiveFilename", MiscellaneousFunctions::GetCaseInsensitiveFilename},\
\
{"GetLastPlayerId", MiscellaneousFunctions::GetLastPlayerId},\
\
{"GetCurrentMpNum", MiscellaneousFunctions::GetCurrentMpNum},\
{"SetCurrentMpNum", MiscellaneousFunctions::SetCurrentMpNum},\
\
{"GetPluginEnforcementState", MiscellaneousFunctions::GetPluginEnforcementState},\
{"SetPluginEnforcementState", MiscellaneousFunctions::SetPluginEnforcementState},\
\
{"LogMessage", MiscellaneousFunctions::LogMessage},\
{"LogAppend", MiscellaneousFunctions::LogAppend}
{"SetCurrentMpNum", MiscellaneousFunctions::SetCurrentMpNum}
class MiscellaneousFunctions
{
public:
/**
* \brief Check whether a certain file exists.
*
* This will be a case sensitive check on case sensitive filesystems.
*
* Whenever you want to enforce case insensitivity, use GetCaseInsensitiveFilename() instead.
*
* \return Whether the file exists or not.
*/
static bool DoesFileExist(const char *filePath) noexcept;
/**
* \brief Get the first filename in a folder that has a case insensitive match with the filename
* argument.
*
* This is used to retain case insensitivity when opening data files on Linux.
*
* \return The filename that matches.
*/
static const char *GetCaseInsensitiveFilename(const char *folderPath, const char *filename) noexcept;
/**
* \brief Get the last player ID currently connected to the server.
*
@ -78,49 +48,6 @@ public:
* \return void
*/
static void SetCurrentMpNum(int mpNum) noexcept;
/**
* \brief Get the plugin enforcement state of the server.
*
* If true, clients are required to use the same plugins as set for the server.
*
* \return The enforcement state.
*/
static int GetPluginEnforcementState() noexcept;
/**
* \brief Set the plugin enforcement state of the server.
*
* If true, clients are required to use the same plugins as set for the server.
*
* \param state The new enforcement state.
* \return void
*/
static void SetPluginEnforcementState(bool state) noexcept;
/**
* \brief Write a log message with its own timestamp.
*
* It will have "[Script]:" prepended to it so as to mark it as a script-generated log message.
*
* \param level The logging level used (0 for LOG_VERBOSE, 1 for LOG_INFO, 2 for LOG_WARN,
* 3 for LOG_ERROR, 4 for LOG_FATAL).
* \param message The message logged.
* \return void
*/
static void LogMessage(unsigned short level, const char *message) noexcept;
/**
* \brief Write a log message without its own timestamp.
*
* It will have "[Script]:" prepended to it so as to mark it as a script-generated log message.
*
* \param level The logging level used (0 for LOG_VERBOSE, 1 for LOG_INFO, 2 for LOG_WARN,
* 3 for LOG_ERROR, 4 for LOG_FATAL).
* \param message The message logged.
* \return void
*/
static void LogAppend(unsigned short level, const char *message) noexcept;
};
#endif //OPENMW_MISCELLANEOUSAPI_HPP

View file

@ -399,6 +399,15 @@ void ObjectFunctions::SetObjectRotation(double x, double y, double z) noexcept
tempObject.position.rot[2] = z;
}
void ObjectFunctions::SetObjectActivatingPid(unsigned short pid) noexcept
{
Player *player;
GET_PLAYER(pid, player, );
tempObject.activatingActor.guid = player->guid;
tempObject.activatingActor.isPlayer = true;
}
void ObjectFunctions::SetObjectDoorState(int doorState) noexcept
{
tempObject.doorState = doorState;

View file

@ -85,6 +85,8 @@
{"SetObjectPosition", ObjectFunctions::SetObjectPosition},\
{"SetObjectRotation", ObjectFunctions::SetObjectRotation},\
\
{"SetObjectActivatingPid", ObjectFunctions::SetObjectActivatingPid},\
\
{"SetObjectDoorState", ObjectFunctions::SetObjectDoorState},\
{"SetObjectDoorTeleportState", ObjectFunctions::SetObjectDoorTeleportState},\
{"SetObjectDoorDestinationCell", ObjectFunctions::SetObjectDoorDestinationCell},\
@ -776,6 +778,15 @@ public:
*/
static void SetObjectRotation(double x, double y, double z) noexcept;
/**
* \brief Set the player ID of the player activating the temporary object stored on the
* server. Currently only used for ObjectActivate packets.
*
* \param pid The pid of the player.
* \return void
*/
static void SetObjectActivatingPid(unsigned short pid) noexcept;
/**
* \brief Set the door state of the temporary object stored on the server.
*

View file

@ -7,20 +7,6 @@
#include <iostream>
using namespace std;
void PositionFunctions::GetPos(unsigned short pid, float *x, float *y, float *z) noexcept
{
*x = 0.00;
*y = 0.00;
*z = 0.00;
Player *player;
GET_PLAYER(pid, player,);
*x = player->position.pos[0];
*y = player->position.pos[1];
*z = player->position.pos[2];
}
double PositionFunctions::GetPosX(unsigned short pid) noexcept
{
Player *player;
@ -69,20 +55,6 @@ double PositionFunctions::GetPreviousCellPosZ(unsigned short pid) noexcept
return player->previousCellPosition.pos[2];
}
void PositionFunctions::GetRot(unsigned short pid, float *x, float *y, float *z) noexcept
{
*x = 0.00;
*y = 0.00;
*z = 0.00;
Player *player;
GET_PLAYER(pid, player, );
*x = player->position.rot[0];
*y = player->position.rot[1];
*z = player->position.rot[2];
}
double PositionFunctions::GetRotX(unsigned short pid) noexcept
{
Player *player;

View file

@ -4,7 +4,6 @@
#include "../Types.hpp"
#define POSITIONAPI \
{"GetPos", PositionFunctions::GetPos},\
{"GetPosX", PositionFunctions::GetPosX},\
{"GetPosY", PositionFunctions::GetPosY},\
{"GetPosZ", PositionFunctions::GetPosZ},\
@ -13,7 +12,6 @@
{"GetPreviousCellPosY", PositionFunctions::GetPreviousCellPosY},\
{"GetPreviousCellPosZ", PositionFunctions::GetPreviousCellPosZ},\
\
{"GetRot", PositionFunctions::GetRot},\
{"GetRotX", PositionFunctions::GetRotX},\
{"GetRotZ", PositionFunctions::GetRotZ},\
\
@ -29,18 +27,6 @@ class PositionFunctions
{
public:
/**
* \brief Assign the player's positional coordinate values to the variables passed as
* parameters.
*
* \param pid The player ID.
* \param x The variable for the X position.
* \param y The variable for the Y position.
* \param z The variable for the Z position.
* \return void
*/
static void GetPos(unsigned short pid, float *x, float *y, float *z) noexcept;
/**
* \brief Get the X position of a player.
*
@ -89,18 +75,6 @@ public:
*/
static double GetPreviousCellPosZ(unsigned short pid) noexcept;
/**
* \brief Assign the player's rotational coordinate values to the variables passed as
* parameters.
*
* \param pid The player ID.
* \param x The variable for the X rotation.
* \param y The variable for the Y rotation.
* \param z The variable for the Z rotation.
* \return void
*/
static void GetRot(unsigned short pid, float *x, float *y, float *z) noexcept;
/**
* \brief Get the X rotation of a player.
*

View file

@ -1,5 +1,6 @@
#include "Server.hpp"
#include <components/misc/stringops.hpp>
#include <components/openmw-mp/NetworkMessages.hpp>
#include <components/openmw-mp/Log.hpp>
#include <components/openmw-mp/Version.hpp>
@ -7,7 +8,20 @@
#include <apps/openmw-mp/Script/ScriptFunctions.hpp>
#include <apps/openmw-mp/Networking.hpp>
#include <apps/openmw-mp/MasterClient.hpp>
#include <Script/Script.hpp>
static std::string tempFilename;
static std::chrono::high_resolution_clock::time_point startupTime = std::chrono::high_resolution_clock::now();
void ServerFunctions::LogMessage(unsigned short level, const char *message) noexcept
{
LOG_MESSAGE_SIMPLE(level, "[Script]: %s", message);
}
void ServerFunctions::LogAppend(unsigned short level, const char *message) noexcept
{
LOG_APPEND(level, "[Script]: %s", message);
}
void ServerFunctions::StopServer(int code) noexcept
{
@ -18,7 +32,10 @@ void ServerFunctions::Kick(unsigned short pid) noexcept
{
Player *player;
GET_PLAYER(pid, player,);
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Kicking player %s (%i)", player->npc.mName.c_str(), player->getId());
mwmp::Networking::getPtr()->kickPlayer(player->guid);
player->setLoadState(Player::KICKED);
}
void ServerFunctions::BanAddress(const char *ipAddress) noexcept
@ -31,6 +48,52 @@ void ServerFunctions::UnbanAddress(const char *ipAddress) noexcept
mwmp::Networking::getPtr()->unbanAddress(ipAddress);
}
bool ServerFunctions::DoesFilePathExist(const char *filePath) noexcept
{
return boost::filesystem::exists(filePath);
}
const char *ServerFunctions::GetCaseInsensitiveFilename(const char *folderPath, const char *filename) noexcept
{
if (!boost::filesystem::exists(folderPath)) return "invalid";
boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
for (boost::filesystem::directory_iterator itr(folderPath); itr != end_itr; ++itr)
{
if (Misc::StringUtils::ciEqual(itr->path().filename().string(), filename))
{
tempFilename = itr->path().filename().string();
return tempFilename.c_str();
}
}
return "invalid";
}
const char* ServerFunctions::GetDataPath() noexcept
{
return Script::GetModDir();
}
unsigned int ServerFunctions::GetMillisecondsSinceServerStart() noexcept
{
std::chrono::high_resolution_clock::time_point currentTime = std::chrono::high_resolution_clock::now();
std::chrono::milliseconds milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startupTime);
return milliseconds.count();
}
const char *ServerFunctions::GetOperatingSystemType() noexcept
{
static const std::string operatingSystemType = Utils::getOperatingSystemType();
return operatingSystemType.c_str();
}
const char *ServerFunctions::GetArchitectureType() noexcept
{
static const std::string architectureType = Utils::getArchitectureType();
return architectureType.c_str();
}
const char *ServerFunctions::GetServerVersion() noexcept
{
return TES3MP_VERSION;
@ -72,6 +135,16 @@ bool ServerFunctions::HasPassword() noexcept
return mwmp::Networking::get().isPassworded();
}
bool ServerFunctions::GetDataFileEnforcementState() noexcept
{
return mwmp::Networking::getPtr()->getDataFileEnforcementState();
}
bool ServerFunctions::GetScriptErrorIgnoringState() noexcept
{
return mwmp::Networking::getPtr()->getScriptErrorIgnoringState();
}
void ServerFunctions::SetGameMode(const char *gameMode) noexcept
{
if (mwmp::Networking::getPtr()->getMasterClient())
@ -89,6 +162,16 @@ void ServerFunctions::SetServerPassword(const char *password) noexcept
mwmp::Networking::getPtr()->setServerPassword(password);
}
void ServerFunctions::SetDataFileEnforcementState(bool state) noexcept
{
mwmp::Networking::getPtr()->setDataFileEnforcementState(state);
}
void ServerFunctions::SetScriptErrorIgnoringState(bool state) noexcept
{
mwmp::Networking::getPtr()->setScriptErrorIgnoringState(state);
}
void ServerFunctions::SetRuleString(const char *key, const char *value) noexcept
{
auto mc = mwmp::Networking::getPtr()->getMasterClient();
@ -102,3 +185,64 @@ void ServerFunctions::SetRuleValue(const char *key, double value) noexcept
if (mc)
mc->SetRuleValue(key, value);
}
void ServerFunctions::AddDataFileRequirement(const char *dataFilename, const char *checksumString) noexcept
{
auto &samples = mwmp::Networking::getPtr()->getSamples();
auto it = std::find_if(samples.begin(), samples.end(), [&dataFilename](mwmp::PacketPreInit::PluginPair &item) {
return item.first == dataFilename;
});
if (it != samples.end())
{
// If this is a filename we've added before, ensure our new checksumString for it isn't empty
if (strlen(checksumString) != 0)
it->second.push_back((unsigned)std::stoul(checksumString));
}
else
{
mwmp::PacketPreInit::HashList checksumList;
unsigned checksum = 0;
if (strlen(checksumString) != 0)
{
checksum = (unsigned) std::stoul(checksumString);
checksumList.push_back(checksum);
}
samples.emplace_back(dataFilename, checksumList);
auto masterClient = mwmp::Networking::getPtr()->getMasterClient();
if (masterClient)
masterClient->PushPlugin({dataFilename, checksum});
}
}
// All methods below are deprecated versions of methods from above
bool ServerFunctions::DoesFileExist(const char *filePath) noexcept
{
return DoesFilePathExist(filePath);
}
const char* ServerFunctions::GetModDir() noexcept
{
return GetDataPath();
}
bool ServerFunctions::GetPluginEnforcementState() noexcept
{
return mwmp::Networking::getPtr()->getDataFileEnforcementState();
}
void ServerFunctions::SetPluginEnforcementState(bool state) noexcept
{
SetDataFileEnforcementState(state);
}
void ServerFunctions::AddPluginHash(const char *pluginName, const char *checksumString) noexcept
{
AddDataFileRequirement(pluginName, checksumString);
}

View file

@ -4,30 +4,75 @@
#include "../Types.hpp"
#define SERVERAPI \
{"StopServer", ServerFunctions::StopServer},\
{"LogMessage", ServerFunctions::LogMessage},\
{"LogAppend", ServerFunctions::LogAppend},\
\
{"Kick", ServerFunctions::Kick},\
{"BanAddress", ServerFunctions::BanAddress},\
{"UnbanAddress", ServerFunctions::UnbanAddress},\
{"StopServer", ServerFunctions::StopServer},\
\
{"GetServerVersion", ServerFunctions::GetServerVersion},\
{"GetProtocolVersion", ServerFunctions::GetProtocolVersion},\
{"GetAvgPing", ServerFunctions::GetAvgPing},\
{"GetIP", ServerFunctions::GetIP},\
{"GetMaxPlayers", ServerFunctions::GetMaxPlayers},\
{"GetPort", ServerFunctions::GetPort},\
{"HasPassword", ServerFunctions::HasPassword},\
{"Kick", ServerFunctions::Kick},\
{"BanAddress", ServerFunctions::BanAddress},\
{"UnbanAddress", ServerFunctions::UnbanAddress},\
\
{"SetGameMode", ServerFunctions::SetGameMode},\
{"SetHostname", ServerFunctions::SetHostname},\
{"SetServerPassword", ServerFunctions::SetServerPassword},\
{"SetRuleString", ServerFunctions::SetRuleString},\
{"SetRuleValue", ServerFunctions::SetRuleValue}
{"DoesFilePathExist", ServerFunctions::DoesFilePathExist},\
{"GetCaseInsensitiveFilename", ServerFunctions::GetCaseInsensitiveFilename},\
{"GetDataPath", ServerFunctions::GetDataPath},\
{"GetMillisecondsSinceServerStart", ServerFunctions::GetMillisecondsSinceServerStart},\
{"GetOperatingSystemType", ServerFunctions::GetOperatingSystemType},\
{"GetArchitectureType", ServerFunctions::GetArchitectureType},\
{"GetServerVersion", ServerFunctions::GetServerVersion},\
{"GetProtocolVersion", ServerFunctions::GetProtocolVersion},\
{"GetAvgPing", ServerFunctions::GetAvgPing},\
{"GetIP", ServerFunctions::GetIP},\
{"GetMaxPlayers", ServerFunctions::GetMaxPlayers},\
{"GetPort", ServerFunctions::GetPort},\
{"HasPassword", ServerFunctions::HasPassword},\
{"GetDataFileEnforcementState", ServerFunctions::GetDataFileEnforcementState},\
{"GetScriptErrorIgnoringState", ServerFunctions::GetScriptErrorIgnoringState},\
\
{"SetGameMode", ServerFunctions::SetGameMode},\
{"SetHostname", ServerFunctions::SetHostname},\
{"SetServerPassword", ServerFunctions::SetServerPassword},\
{"SetDataFileEnforcementState", ServerFunctions::SetDataFileEnforcementState},\
{"SetScriptErrorIgnoringState", ServerFunctions::SetScriptErrorIgnoringState},\
{"SetRuleString", ServerFunctions::SetRuleString},\
{"SetRuleValue", ServerFunctions::SetRuleValue},\
\
{"AddDataFileRequirement", ServerFunctions::AddDataFileRequirement},\
\
{"DoesFileExist", ServerFunctions::DoesFileExist},\
{"GetModDir", ServerFunctions::GetModDir},\
{"GetPluginEnforcementState", ServerFunctions::GetPluginEnforcementState},\
{"SetPluginEnforcementState", ServerFunctions::SetPluginEnforcementState},\
{"AddPluginHash", ServerFunctions::AddPluginHash}
class ServerFunctions
{
public:
/**
* \brief Write a log message with its own timestamp.
*
* It will have "[Script]:" prepended to it so as to mark it as a script-generated log message.
*
* \param level The logging level used (0 for LOG_VERBOSE, 1 for LOG_INFO, 2 for LOG_WARN,
* 3 for LOG_ERROR, 4 for LOG_FATAL).
* \param message The message logged.
* \return void
*/
static void LogMessage(unsigned short level, const char *message) noexcept;
/**
* \brief Write a log message without its own timestamp.
*
* It will have "[Script]:" prepended to it so as to mark it as a script-generated log message.
*
* \param level The logging level used (0 for LOG_VERBOSE, 1 for LOG_INFO, 2 for LOG_WARN,
* 3 for LOG_ERROR, 4 for LOG_FATAL).
* \param message The message logged.
* \return void
*/
static void LogAppend(unsigned short level, const char *message) noexcept;
/**
* \brief Shut down the server.
*
@ -60,6 +105,59 @@ public:
*/
static void UnbanAddress(const char *ipAddress) noexcept;
/**
* \brief Check whether a certain file path exists.
*
* This will be a case sensitive check on case sensitive filesystems.
*
* Whenever you want to enforce case insensitivity, use GetCaseInsensitiveFilename() instead.
*
* \return Whether the file exists or not.
*/
static bool DoesFilePathExist(const char *filePath) noexcept;
/**
* \brief Get the first filename in a folder that has a case insensitive match with the filename
* argument.
*
* This is used to retain case insensitivity when opening data files on Linux.
*
* \return The filename that matches.
*/
static const char *GetCaseInsensitiveFilename(const char *folderPath, const char *filename) noexcept;
/**
* \brief Get the path of the server's data folder.
*
* \return The data path.
*/
static const char *GetDataPath() noexcept;
/**
* \brief Get the milliseconds elapsed since the server was started.
*
* \return The time since the server's startup in milliseconds.
*/
static unsigned int GetMillisecondsSinceServerStart() noexcept;
/**
* \brief Get the type of the operating system used by the server.
*
* Note: Currently, the type can be "Windows", "Linux", "OS X" or "Unknown OS".
*
* \return The type of the operating system.
*/
static const char *GetOperatingSystemType() noexcept;
/**
* \brief Get the architecture type used by the server.
*
* Note: Currently, the type can be "64-bit", "32-bit", "ARMv#" or "Unknown architecture".
*
* \return The architecture type.
*/
static const char *GetArchitectureType() noexcept;
/**
* \brief Get the TES3MP version of the server.
*
@ -93,7 +191,7 @@ public:
/**
* \brief Get the port used by the server.
*
* \return Port
* \return The port.
*/
static unsigned short GetPort() noexcept;
@ -111,6 +209,24 @@ public:
*/
static bool HasPassword() noexcept;
/**
* \brief Get the data file enforcement state of the server.
*
* If true, clients are required to use the same data files as set for the server.
*
* \return The enforcement state.
*/
static bool GetDataFileEnforcementState() noexcept;
/**
* \brief Get the script error ignoring state of the server.
*
* If true, script errors will not crash the server.
*
* \return The script error ignoring state.
*/
static bool GetScriptErrorIgnoringState() noexcept;
/**
* \brief Set the game mode of the server, as displayed in the server browser.
*
@ -133,7 +249,29 @@ public:
* \param password The password.
* \return void
*/
static void SetServerPassword(const char *passw) noexcept;
static void SetServerPassword(const char *password) noexcept;
/**
* \brief Set the data file enforcement state of the server.
*
* If true, clients are required to use the same data files as set for the server.
*
* \param state The new enforcement state.
* \return void
*/
static void SetDataFileEnforcementState(bool state) noexcept;
/**
* \brief Set whether script errors should be ignored or not.
*
* If true, script errors will not crash the server, but could have any number
* of unforeseen consequences, which is why this is a highly experimental
* setting.
*
* \param state The new script error ignoring state.
* \return void
*/
static void SetScriptErrorIgnoringState(bool state) noexcept;
/**
* \brief Set a rule string for the server details displayed in the server browser.
@ -152,6 +290,28 @@ public:
* \return void
*/
static void SetRuleValue(const char *key, double value) noexcept;
/**
* \brief Add a data file and a corresponding CRC32 checksum to the data file loadout
* that connecting clients need to match.
*
* It can be used multiple times to set multiple checksums for the same data file.
*
* Note: If an empty string is provided for the checksum, a checksum will not be
* required for that data file.
*
* @param dataFilename The filename of the data file.
* @param checksumString A string with the CRC32 checksum required.
*/
static void AddDataFileRequirement(const char *dataFilename, const char *checksumString) noexcept;
// All methods below are deprecated versions of methods from above
static bool DoesFileExist(const char *filePath) noexcept;
static const char *GetModDir() noexcept;
static bool GetPluginEnforcementState() noexcept;
static void SetPluginEnforcementState(bool state) noexcept;
static void AddPluginHash(const char *pluginName, const char *checksumString) noexcept;
};
#endif //OPENMW_SERVERAPI_HPP

View file

@ -201,6 +201,17 @@ int StatsFunctions::GetAttributeModifier(unsigned short pid, unsigned short attr
return player->creatureStats.mAttributes[attributeId].mMod;
}
double StatsFunctions::GetAttributeDamage(unsigned short pid, unsigned short attributeId) noexcept
{
Player *player;
GET_PLAYER(pid, player, 0);
if (attributeId >= Attribute::Length)
return 0;
return player->creatureStats.mAttributes[attributeId].mDamage;
}
int StatsFunctions::GetSkillBase(unsigned short pid, unsigned short skillId) noexcept
{
Player *player;
@ -223,6 +234,17 @@ int StatsFunctions::GetSkillModifier(unsigned short pid, unsigned short skillId)
return player->npcStats.mSkills[skillId].mMod;
}
double StatsFunctions::GetSkillDamage(unsigned short pid, unsigned short skillId) noexcept
{
Player *player;
GET_PLAYER(pid, player, 0);
if (skillId >= Skill::Length)
return 0;
return player->npcStats.mSkills[skillId].mDamage;
}
double StatsFunctions::GetSkillProgress(unsigned short pid, unsigned short skillId) noexcept
{
Player *player;
@ -437,6 +459,20 @@ void StatsFunctions::ClearAttributeModifier(unsigned short pid, unsigned short a
player->attributeIndexChanges.push_back(attributeId);
}
void StatsFunctions::SetAttributeDamage(unsigned short pid, unsigned short attributeId, double value) noexcept
{
Player *player;
GET_PLAYER(pid, player, );
if (attributeId >= Attribute::Length)
return;
player->creatureStats.mAttributes[attributeId].mDamage = value;
if (!Utils::vectorContains(player->attributeIndexChanges, attributeId))
player->attributeIndexChanges.push_back(attributeId);
}
void StatsFunctions::SetSkillBase(unsigned short pid, unsigned short skillId, int value) noexcept
{
Player *player;
@ -465,6 +501,20 @@ void StatsFunctions::ClearSkillModifier(unsigned short pid, unsigned short skill
player->skillIndexChanges.push_back(skillId);
}
void StatsFunctions::SetSkillDamage(unsigned short pid, unsigned short skillId, double value) noexcept
{
Player *player;
GET_PLAYER(pid, player, );
if (skillId >= Skill::Length)
return;
player->npcStats.mSkills[skillId].mDamage = value;
if (!Utils::vectorContains(player->skillIndexChanges, skillId))
player->skillIndexChanges.push_back(skillId);
}
void StatsFunctions::SetSkillProgress(unsigned short pid, unsigned short skillId, double value) noexcept
{
Player *player;

View file

@ -30,9 +30,11 @@
\
{"GetAttributeBase", StatsFunctions::GetAttributeBase},\
{"GetAttributeModifier", StatsFunctions::GetAttributeModifier},\
{"GetAttributeDamage", StatsFunctions::GetAttributeDamage},\
\
{"GetSkillBase", StatsFunctions::GetSkillBase},\
{"GetSkillModifier", StatsFunctions::GetSkillModifier},\
{"GetSkillDamage", StatsFunctions::GetSkillDamage},\
{"GetSkillProgress", StatsFunctions::GetSkillProgress},\
{"GetSkillIncrease", StatsFunctions::GetSkillIncrease},\
\
@ -58,9 +60,11 @@
\
{"SetAttributeBase", StatsFunctions::SetAttributeBase},\
{"ClearAttributeModifier", StatsFunctions::ClearAttributeModifier},\
{"SetAttributeDamage", StatsFunctions::SetAttributeDamage},\
\
{"SetSkillBase", StatsFunctions::SetSkillBase},\
{"ClearSkillModifier", StatsFunctions::ClearSkillModifier},\
{"SetSkillDamage", StatsFunctions::SetSkillDamage},\
{"SetSkillProgress", StatsFunctions::SetSkillProgress},\
{"SetSkillIncrease", StatsFunctions::SetSkillIncrease},\
\
@ -267,6 +271,16 @@ public:
*/
static int GetAttributeModifier(unsigned short pid, unsigned short attributeId) noexcept;
/**
* \brief Get the amount of damage (as caused through the Damage Attribute effect)
* to a player's attribute.
*
* \param pid The player ID.
* \param attributeId The attribute ID.
* \return The amount of damage to the attribute.
*/
static double GetAttributeDamage(unsigned short pid, unsigned short attributeId) noexcept;
/**
* \brief Get the base value of a player's skill.
*
@ -285,6 +299,16 @@ public:
*/
static int GetSkillModifier(unsigned short pid, unsigned short skillId) noexcept;
/**
* \brief Get the amount of damage (as caused through the Damage Skill effect)
* to a player's skill.
*
* \param pid The player ID.
* \param skillId The skill ID.
* \return The amount of damage to the skill.
*/
static double GetSkillDamage(unsigned short pid, unsigned short skillId) noexcept;
/**
* \brief Get the progress the player has made towards increasing a certain skill by 1.
*
@ -477,6 +501,17 @@ public:
*/
static void ClearAttributeModifier(unsigned short pid, unsigned short attributeId) noexcept;
/**
* \brief Set the amount of damage (as caused through the Damage Attribute effect) to
* a player's attribute.
*
* \param pid The player ID.
* \param attributeId The attribute ID.
* \param value The amount of damage to the player's attribute.
* \return void
*/
static void SetAttributeDamage(unsigned short pid, unsigned short attributeId, double value) noexcept;
/**
* \brief Set the base value of a player's skill.
*
@ -501,6 +536,17 @@ public:
*/
static void ClearSkillModifier(unsigned short pid, unsigned short skillId) noexcept;
/**
* \brief Set the amount of damage (as caused through the Damage Skill effect) to
* a player's skill.
*
* \param pid The player ID.
* \param skillId The skill ID.
* \param value The amount of damage to the player's skill.
* \return void
*/
static void SetSkillDamage(unsigned short pid, unsigned short skillId, double value) noexcept;
/**
* \brief Set the progress the player has made towards increasing a certain skill by 1.
*

View file

@ -21,7 +21,7 @@ int ScriptFunctions::CreateTimerEx(ScriptFunc callback, int msec, const char *ty
try
{
vector<boost::any> params;
GetArguments(params, args, types);
Utils::getArguments(params, args, types);
return mwmp::TimerAPI::CreateTimer(callback, msec, types, params);
}
@ -54,5 +54,5 @@ void ScriptFunctions::FreeTimer(int timerId) noexcept
bool ScriptFunctions::IsTimerElapsed(int timerId) noexcept
{
return TimerAPI::IsEndTimer(timerId);
return TimerAPI::IsTimerElapsed(timerId);
}

View file

@ -9,6 +9,24 @@
using namespace std;
std::set<std::string> LangLua::packagePath;
std::set<std::string> LangLua::packageCPath;
void setLuaPath(lua_State* L, const char* path, bool cpath = false)
{
string field = cpath ? "cpath" : "path";
lua_getglobal(L, "package");
lua_getfield(L, -1, field.c_str());
std::string cur_path = lua_tostring(L, -1);
cur_path.append(";");
cur_path.append(path);
lua_pop(L, 1);
lua_pushstring(L, cur_path.c_str());
lua_setfield(L, -2, field.c_str());
lua_pop(L, 1);
}
lib_t LangLua::GetInterface()
{
return reinterpret_cast<lib_t>(lua);
@ -23,6 +41,17 @@ LangLua::LangLua()
{
lua = luaL_newstate();
luaL_openlibs(lua); // load all lua std libs
std::string p, cp;
for (auto& path : packagePath)
p += path + ';';
for (auto& path : packageCPath)
cp += path + ';';
setLuaPath(lua, p.c_str());
setLuaPath(lua, cp.c_str(), true);
}
LangLua::~LangLua()
@ -78,8 +107,18 @@ template<> struct F_<1> { static constexpr LuaFuctionData F{"CreateTimerEx", Lan
template<> struct F_<2> { static constexpr LuaFuctionData F{"MakePublic", LangLua::MakePublic}; };
template<> struct F_<3> { static constexpr LuaFuctionData F{"CallPublic", LangLua::CallPublic}; };
#ifdef __arm__
template<std::size_t... Is>
struct indices {};
template<std::size_t N, std::size_t... Is>
struct build_indices : build_indices<N-1, N-1, Is...> {};
template<std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...> {};
template<std::size_t N>
using IndicesFor = build_indices<N>;
template<size_t... Indices>
inline LuaFuctionData *LangLua::functions(indices<Indices...>)
LuaFuctionData *functions(indices<Indices...>)
{
static LuaFuctionData functions_[sizeof...(Indices)]{
@ -93,6 +132,41 @@ inline LuaFuctionData *LangLua::functions(indices<Indices...>)
return functions_;
}
#else
template<unsigned int I>
struct C
{
constexpr static void Fn(LuaFuctionData *functions_)
{
functions_[I] = F_<I>::F;
C<I - 1>::Fn(functions_);
}
};
template<>
struct C<0>
{
constexpr static void Fn(LuaFuctionData *functions_)
{
functions_[0] = F_<0>::F;
}
};
template<size_t LastI>
LuaFuctionData *functions()
{
static LuaFuctionData functions_[LastI];
C<LastI - 1>::Fn(functions_);
static_assert(
sizeof(functions_) / sizeof(functions_[0]) ==
sizeof(ScriptFunctions::functions) / sizeof(ScriptFunctions::functions[0]),
"Not all functions have been mapped to Lua");
return functions_;
}
#endif
void LangLua::LoadProgram(const char *filename)
{
@ -104,8 +178,11 @@ void LangLua::LoadProgram(const char *filename)
constexpr auto functions_n = sizeof(ScriptFunctions::functions) / sizeof(ScriptFunctions::functions[0]);
#ifdef __arm__
LuaFuctionData *functions_ = functions(IndicesFor<functions_n>{});
#else
LuaFuctionData *functions_ = functions<sizeof(ScriptFunctions::functions) / sizeof(ScriptFunctions::functions[0])>();
#endif
luabridge::Namespace tes3mp = luabridge::getGlobalNamespace(lua).beginNamespace("tes3mp");
for (unsigned i = 0; i < functions_n; i++)
@ -133,20 +210,65 @@ boost::any LangLua::Call(const char *name, const char *argl, int buf, ...)
{
va_list vargs;
va_start(vargs, buf);
std::vector<boost::any> args;
ScriptFunctions::GetArguments(args, vargs, argl);
int n_args = (int)(strlen(argl));
return Call(name, argl, args);
lua_getglobal(lua, name);
for (int index = 0; index < n_args; index++)
{
switch (argl[index])
{
case 'i':
luabridge::Stack<unsigned int>::push(lua,va_arg(vargs, unsigned int));
break;
case 'q':
luabridge::Stack<signed int>::push(lua,va_arg(vargs, signed int));
break;
case 'l':
luabridge::Stack<unsigned long long>::push(lua, va_arg(vargs, unsigned long long));
break;
case 'w':
luabridge::Stack<signed long long>::push(lua, va_arg(vargs, signed long long));
break;
case 'f':
luabridge::Stack<double>::push(lua, va_arg(vargs, double));
break;
case 'p':
luabridge::Stack<void*>::push(lua, va_arg(vargs, void*));
break;
case 's':
luabridge::Stack<const char*>::push(lua, va_arg(vargs, const char*));
break;
case 'b':
luabridge::Stack<bool>::push(lua, (bool) va_arg(vargs, int));
break;
default:
throw runtime_error("C++ call: Unknown argument identifier " + argl[index]);
}
}
va_end(vargs);
luabridge::LuaException::pcall(lua, n_args, 1);
return boost::any(luabridge::LuaRef::fromStack(lua, -1));
}
boost::any LangLua::Call(const char *name, const char *argl, const std::vector<boost::any> &args)
{
int n_args = (int)(strlen(argl)) ;
int n_args = (int)(strlen(argl));
lua_getglobal (lua, name);
lua_getglobal(lua, name);
for (intptr_t index = 0; index < n_args; index++)
for (int index = 0; index < n_args; index++)
{
switch (argl[index])
{
@ -186,6 +308,16 @@ boost::any LangLua::Call(const char *name, const char *argl, const std::vector<b
}
}
luabridge::LuaException::pcall (lua, n_args, 1);
luabridge::LuaException::pcall(lua, n_args, 1);
return boost::any(luabridge::LuaRef::fromStack(lua, -1));
}
void LangLua::AddPackagePath(const std::string& path)
{
packagePath.emplace(path);
}
void LangLua::AddPackageCPath(const std::string& path)
{
packageCPath.emplace(path);
}

View file

@ -9,6 +9,7 @@
#include <extern/LuaBridge/LuaBridge.h>
#include <LuaBridge.h>
#include <set>
#include <boost/any.hpp>
#include "../ScriptFunction.hpp"
@ -22,25 +23,17 @@ struct LuaFuctionData
class LangLua: public Language
{
private:
template<std::size_t... Is>
struct indices {};
template<std::size_t N, std::size_t... Is>
struct build_indices : build_indices<N-1, N-1, Is...> {};
template<std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...> {};
template<std::size_t N>
using IndicesFor = build_indices<N>;
public:
virtual lib_t GetInterface() override;
template<std::size_t... Indices>
static LuaFuctionData* functions(indices<Indices...>);
lua_State *lua;
public:
LangLua();
LangLua(lua_State *lua);
~LangLua();
static void AddPackagePath(const std::string &path);
static void AddPackageCPath(const std::string &path);
static int MakePublic(lua_State *lua) noexcept;
static int CallPublic(lua_State *lua);
@ -52,6 +45,9 @@ public:
virtual bool IsCallbackPresent(const char *name) override;
virtual boost::any Call(const char *name, const char *argl, int buf, ...) override;
virtual boost::any Call(const char *name, const char *argl, const std::vector<boost::any> &args) override;
private:
static std::set<std::string> packageCPath;
static std::set<std::string> packagePath;
};

View file

@ -12,6 +12,7 @@
using namespace std;
Script::ScriptList Script::scripts;
std::string Script::moddir;
Script::Script(const char *path)
{
@ -94,3 +95,14 @@ void Script::LoadScript(const char *script, const char *base)
snprintf(path, sizeof(path), Utils::convertPath("%s/%s/%s").c_str(), base, "scripts", script);
Script::scripts.emplace_back(new Script(path));
}
void Script::SetModDir(const std::string &moddir)
{
if (Script::moddir.empty()) // do not allow to change in runtime
Script::moddir = moddir;
}
const char* Script::GetModDir()
{
return moddir.c_str();
}

View file

@ -4,15 +4,18 @@
#ifndef PLUGINSYSTEM3_SCRIPT_HPP
#define PLUGINSYSTEM3_SCRIPT_HPP
#include <boost/any.hpp>
#include <unordered_map>
#include <memory>
#include "Types.hpp"
#include "SystemInterface.hpp"
#include "ScriptFunction.hpp"
#include "ScriptFunctions.hpp"
#include "Language.hpp"
#include <boost/any.hpp>
#include <unordered_map>
#include <memory>
#include "Networking.hpp"
class Script : private ScriptFunctions
{
@ -51,60 +54,27 @@ private:
Script(const Script&) = delete;
Script& operator=(const Script&) = delete;
protected:
static std::string moddir;
public:
~Script();
static void LoadScript(const char *script, const char* base);
static void LoadScripts(char* scripts, const char* base);
static void UnloadScripts();
static void SetModDir(const std::string &moddir);
static const char* GetModDir();
static constexpr ScriptCallbackData const& CallBackData(const unsigned int I, const unsigned int N = 0) {
return callbacks[N].index == I ? callbacks[N] : CallBackData(I, N + 1);
}
template<unsigned int I>
using CallBackReturn = typename CharType<CallBackData(I).callback.ret>::type;
template<size_t N>
static constexpr unsigned int CallbackIdentity(const char(&str)[N])
{
return Utils::hash(str);
}
template<unsigned int I, bool B = false, typename... Args>
static unsigned int Call(CallBackReturn<I>& result, Args&&... args) {
constexpr ScriptCallbackData const& data = CallBackData(I);
static_assert(data.callback.matches(TypeString<typename std::remove_reference<Args>::type...>::value),
"Wrong number or types of arguments");
unsigned int count = 0;
for (auto& script : scripts)
{
if (!script->callbacks_.count(I))
script->callbacks_.emplace(I, script->GetScript<FunctionEllipsis<void>>(data.name));
auto callback = script->callbacks_[I];
if (!callback)
continue;
if (script->script_type == SCRIPT_CPP)
result = reinterpret_cast<FunctionEllipsis<CallBackReturn<I>>>(callback)(std::forward<Args>(args)...);
#if defined (ENABLE_LUA)
else if (script->script_type == SCRIPT_LUA)
{
boost::any any = script->lang->Call(data.name, data.callback.types, B, std::forward<Args>(args)...);
result = static_cast<CallBackReturn<I>>(boost::any_cast<luabridge::LuaRef>(any).cast<CallBackReturn<I>>());
}
#endif
++count;
}
return count;
}
template<unsigned int I, bool B = false, typename... Args>
static unsigned int Call(Args&&... args) {
constexpr ScriptCallbackData const& data = CallBackData(I);
@ -124,10 +94,23 @@ public:
continue;
if (script->script_type == SCRIPT_CPP)
reinterpret_cast<FunctionEllipsis<CallBackReturn<I>>>(callback)(std::forward<Args>(args)...);
(callback)(std::forward<Args>(args)...);
#if defined (ENABLE_LUA)
else if (script->script_type == SCRIPT_LUA)
script->lang->Call(data.name, data.callback.types, B, std::forward<Args>(args)...);
{
try
{
script->lang->Call(data.name, data.callback.types, B, std::forward<Args>(args)...);
}
catch (std::exception &e)
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, e.what());
Script::Call<Script::CallbackIdentity("OnServerScriptCrash")>(e.what());
if (!mwmp::Networking::getPtr()->getScriptErrorIgnoringState())
throw;
}
}
#endif
++count;
}

View file

@ -6,10 +6,6 @@
#include <stdexcept>
#include "ScriptFunction.hpp"
#if !defined(_WIN32) && !defined(__ARM_ARCH) // temporarily disabled
#include <call.hpp>
#endif
#if defined (ENABLE_LUA)
#include "LangLua/LangLua.hpp"
#endif
@ -70,48 +66,10 @@ boost::any ScriptFunction::Call(const vector<boost::any> &args)
default:
throw runtime_error("Lua call: Unknown return type" + ret_type);
}
}
#endif
else
{
#if !defined(_WIN32) && !defined(__ARM_ARCH) // temporarily disabled
string::iterator it;
vector<boost::any>::const_iterator it2;
vector<intptr_t> data;
CallArgs callArgs;
for (it = def.begin(), it2 = args.begin(); it != def.end(); ++it, ++it2)
{
switch (*it)
{
case 'i':
callArgs.push_integer(boost::any_cast<unsigned int>(*it2));
break;
case 'q':
callArgs.push_integer(boost::any_cast<signed int>(*it2));
break;
case 'f':
callArgs.push_double(boost::any_cast<double>(*it2));
break;
case 'd':
callArgs.push_double(boost::any_cast<double*>(*it2));
break;
case 's':
callArgs.push_stringPtr(boost::any_cast<const char *>(*it2));
break;
case 'v':
result = boost::any();
break;
default:
throw runtime_error("C++ call: Unknown argument identifier " + *it);
}
}
Func f = reinterpret_cast<Func>(fCpp);
result = ::Call(f, callArgs);
#else
throw runtime_error("C++ call: Windows and ARM not supported yet.");
#endif
lua_settop(fLua.lua, 0);
}
#endif
return result;
}

View file

@ -13,62 +13,6 @@ constexpr ScriptCallbackData ScriptFunctions::callbacks[];
using namespace std;
void ScriptFunctions::GetArguments(std::vector<boost::any> &params, va_list args, const std::string &def)
{
params.reserve(def.length());
try
{
for (char c : def)
{
switch (c)
{
case 'i':
params.emplace_back(va_arg(args, unsigned int));
break;
case 'q':
params.emplace_back(va_arg(args, signed int));
break;
case 'l':
params.emplace_back(va_arg(args, unsigned long long));
break;
case 'w':
params.emplace_back(va_arg(args, signed long long));
break;
case 'f':
params.emplace_back(va_arg(args, double));
break;
case 'p':
params.emplace_back(va_arg(args, void*));
break;
case 's':
params.emplace_back(va_arg(args, const char*));
break;
case 'b':
params.emplace_back(va_arg(args, int));
break;
default:
throw runtime_error("C++ call: Unknown argument identifier " + c);
}
}
}
catch (...)
{
va_end(args);
throw;
}
va_end(args);
}
void ScriptFunctions::MakePublic(ScriptFunc _public, const char *name, char ret_type, const char *def) noexcept
{
Public::MakePublic(_public, name, ret_type, def);
@ -81,7 +25,7 @@ boost::any ScriptFunctions::CallPublic(const char *name, va_list args) noexcept
try
{
string def = Public::GetDefinition(name);
GetArguments(params, args, def);
Utils::getArguments(params, args, def);
return Public::Call(name, params);
}

View file

@ -30,6 +30,10 @@
#include <components/openmw-mp/Log.hpp>
#ifndef __PRETTY_FUNCTION__
#define __PRETTY_FUNCTION__ __FUNCTION__
#endif
#define GET_PLAYER(pid, pl, retvalue) \
pl = Players::getPlayer(pid); \
if (player == 0) {\
@ -43,7 +47,6 @@ class ScriptFunctions
{
public:
static void GetArguments(std::vector<boost::any> &params, va_list args, const std::string &def);
static void MakePublic(ScriptFunc _public, const char *name, char ret_type, const char *def) noexcept;
static boost::any CallPublic(const char *name, va_list args) noexcept;
@ -116,9 +119,9 @@ public:
static constexpr ScriptFunctionData functions[]{
{"CreateTimer", ScriptFunctions::CreateTimer},
{"CreateTimerEx", reinterpret_cast<Function<void>>(ScriptFunctions::CreateTimerEx)},
{"CreateTimerEx", ScriptFunctions::CreateTimerEx},
{"MakePublic", ScriptFunctions::MakePublic},
{"CallPublic", reinterpret_cast<Function<void>>(ScriptFunctions::CallPublic)},
{"CallPublic", ScriptFunctions::CallPublic},
{"StartTimer", ScriptFunctions::StartTimer},
{"StopTimer", ScriptFunctions::StopTimer},
@ -150,63 +153,63 @@ public:
};
static constexpr ScriptCallbackData callbacks[]{
{"Main", Function<int, int, int>()},
{"OnServerInit", Function<void>()},
{"OnServerPostInit", Function<void>()},
{"OnServerExit", Function<void, bool>()},
{"OnPlayerConnect", Function<bool, unsigned short>()},
{"OnPlayerDisconnect", Function<void, unsigned short>()},
{"OnPlayerDeath", Function<void, unsigned short>()},
{"OnPlayerResurrect", Function<void, unsigned short>()},
{"OnPlayerCellChange", Function<void, unsigned short>()},
{"OnPlayerAttribute", Function<void, unsigned short>()},
{"OnPlayerSkill", Function<void, unsigned short>()},
{"OnPlayerLevel", Function<void, unsigned short>()},
{"OnPlayerBounty", Function<void, unsigned short>()},
{"OnPlayerReputation", Function<void, unsigned short>()},
{"OnPlayerEquipment", Function<void, unsigned short>()},
{"OnPlayerInventory", Function<void, unsigned short>()},
{"OnPlayerJournal", Function<void, unsigned short>()},
{"OnPlayerFaction", Function<void, unsigned short>()},
{"OnPlayerShapeshift", Function<void, unsigned short>()},
{"OnPlayerSpellbook", Function<void, unsigned short>()},
{"OnPlayerQuickKeys", Function<void, unsigned short>()},
{"OnPlayerTopic", Function<void, unsigned short>()},
{"OnPlayerDisposition", Function<void, unsigned short>()},
{"OnPlayerBook", Function<void, unsigned short>()},
{"OnPlayerItemUse", Function<void, unsigned short>()},
{"OnPlayerMiscellaneous", Function<void, unsigned short>()},
{"OnPlayerInput", Function<void, unsigned short>()},
{"OnPlayerRest", Function<void, unsigned short>()},
{"OnRecordDynamic", Function<void, unsigned short>()},
{"OnCellLoad", Function<void, unsigned short, const char*>()},
{"OnCellUnload", Function<void, unsigned short, const char*>()},
{"OnCellDeletion", Function<void, const char*>()},
{"OnContainer", Function<void, unsigned short, const char*>()},
{"OnDoorState", Function<void, unsigned short, const char*>()},
{"OnObjectActivate", Function<void, unsigned short, const char*>()},
{"OnObjectPlace", Function<void, unsigned short, const char*>()},
{"OnObjectState", Function<void, unsigned short, const char*>()},
{"OnObjectSpawn", Function<void, unsigned short, const char*>()},
{"OnObjectDelete", Function<void, unsigned short, const char*>()},
{"OnObjectLock", Function<void, unsigned short, const char*>()},
{"OnObjectScale", Function<void, unsigned short, const char*>()},
{"OnObjectTrap", Function<void, unsigned short, const char*>()},
{"OnVideoPlay", Function<void, unsigned short, const char*>()},
{"OnActorList", Function<void, unsigned short, const char*>()},
{"OnActorEquipment", Function<void, unsigned short, const char*>()},
{"OnActorAI", Function<void, unsigned short, const char*>()},
{"OnActorDeath", Function<void, unsigned short, const char*>()},
{"OnActorCellChange", Function<void, unsigned short, const char*>()},
{"OnActorTest", Function<void, unsigned short, const char*>()},
{"OnPlayerSendMessage", Function<bool, unsigned short, const char*>()},
{"OnPlayerEndCharGen", Function<void, unsigned short>()},
{"OnGUIAction", Function<void, unsigned short, int, const char*>()},
{"OnWorldKillCount", Function<void, unsigned short>()},
{"OnWorldMap", Function<void, unsigned short>()},
{"OnWorldWeather", Function<void, unsigned short>() },
{"OnMpNumIncrement", Function<void, int>()},
{"OnRequestPluginList", Function<const char *, unsigned int, unsigned int>()}
{"OnServerInit", Callback<>()},
{"OnServerPostInit", Callback<>()},
{"OnServerExit", Callback<bool>()},
{"OnServerScriptCrash", Callback<const char*>()},
{"OnPlayerConnect", Callback<unsigned short>()},
{"OnPlayerDisconnect", Callback<unsigned short>()},
{"OnPlayerDeath", Callback<unsigned short>()},
{"OnPlayerResurrect", Callback<unsigned short>()},
{"OnPlayerCellChange", Callback<unsigned short>()},
{"OnPlayerAttribute", Callback<unsigned short>()},
{"OnPlayerSkill", Callback<unsigned short>()},
{"OnPlayerLevel", Callback<unsigned short>()},
{"OnPlayerBounty", Callback<unsigned short>()},
{"OnPlayerReputation", Callback<unsigned short>()},
{"OnPlayerEquipment", Callback<unsigned short>()},
{"OnPlayerInventory", Callback<unsigned short>()},
{"OnPlayerJournal", Callback<unsigned short>()},
{"OnPlayerFaction", Callback<unsigned short>()},
{"OnPlayerShapeshift", Callback<unsigned short>()},
{"OnPlayerSpellbook", Callback<unsigned short>()},
{"OnPlayerQuickKeys", Callback<unsigned short>()},
{"OnPlayerTopic", Callback<unsigned short>()},
{"OnPlayerDisposition", Callback<unsigned short>()},
{"OnPlayerBook", Callback<unsigned short>()},
{"OnPlayerItemUse", Callback<unsigned short>()},
{"OnPlayerMiscellaneous", Callback<unsigned short>()},
{"OnPlayerInput", Callback<unsigned short>()},
{"OnPlayerRest", Callback<unsigned short>()},
{"OnRecordDynamic", Callback<unsigned short>()},
{"OnCellLoad", Callback<unsigned short, const char*>()},
{"OnCellUnload", Callback<unsigned short, const char*>()},
{"OnCellDeletion", Callback<const char*>()},
{"OnContainer", Callback<unsigned short, const char*>()},
{"OnDoorState", Callback<unsigned short, const char*>()},
{"OnObjectActivate", Callback<unsigned short, const char*>()},
{"OnObjectPlace", Callback<unsigned short, const char*>()},
{"OnObjectState", Callback<unsigned short, const char*>()},
{"OnObjectSpawn", Callback<unsigned short, const char*>()},
{"OnObjectDelete", Callback<unsigned short, const char*>()},
{"OnObjectLock", Callback<unsigned short, const char*>()},
{"OnObjectScale", Callback<unsigned short, const char*>()},
{"OnObjectTrap", Callback<unsigned short, const char*>()},
{"OnVideoPlay", Callback<unsigned short, const char*>()},
{"OnActorList", Callback<unsigned short, const char*>()},
{"OnActorEquipment", Callback<unsigned short, const char*>()},
{"OnActorAI", Callback<unsigned short, const char*>()},
{"OnActorDeath", Callback<unsigned short, const char*>()},
{"OnActorCellChange", Callback<unsigned short, const char*>()},
{"OnActorTest", Callback<unsigned short, const char*>()},
{"OnPlayerSendMessage", Callback<unsigned short, const char*>()},
{"OnPlayerEndCharGen", Callback<unsigned short>()},
{"OnGUIAction", Callback<unsigned short, int, const char*>()},
{"OnWorldKillCount", Callback<unsigned short>()},
{"OnWorldMap", Callback<unsigned short>()},
{"OnWorldWeather", Callback<unsigned short>() },
{"OnMpNumIncrement", Callback<int>()},
{"OnRequestDataFileList", Callback<>()}
};
};

View file

@ -82,14 +82,34 @@ struct ScriptIdentity
constexpr ScriptIdentity(Function<R, Types...>) : types(TypeString<Types...>::value), ret(TypeChar<R, sizeof_void<R>::value>::value), numargs(sizeof(TypeString<Types...>::value) - 1) {}
};
template<typename... Types>
using Callback = void (*)(Types...);
struct CallbackIdentity
{
const char* types;
const unsigned int numargs;
constexpr bool matches(const char* types, const unsigned int N = 0) const
{
return N < numargs ? this->types[N] == types[N] && matches(types, N + 1) : this->types[N] == types[N];
}
template<typename... Types>
constexpr CallbackIdentity(Callback<Types...>) : types(TypeString<Types...>::value), numargs(sizeof(TypeString<Types...>::value) - 1) {}
};
struct ScriptFunctionPointer : public ScriptIdentity
{
Function<void> addr;
void *addr;
#if (!defined(__clang__) && defined(__GNUC__))
template<typename R, typename... Types>
constexpr ScriptFunctionPointer(Function<R, Types...> addr) : ScriptIdentity(addr), addr(reinterpret_cast<Function<void>>(addr)) {}
constexpr ScriptFunctionPointer(Function<R, Types...> addr) : ScriptIdentity(addr), addr((void*)(addr)) {}
#else
template<typename R, typename... Types>
constexpr ScriptFunctionPointer(Function<R, Types...> addr) : ScriptIdentity(addr), addr(addr) {}
#endif
};
struct ScriptFunctionData
@ -104,10 +124,10 @@ struct ScriptCallbackData
{
const char* name;
const unsigned int index;
const ScriptIdentity callback;
const CallbackIdentity callback;
template<size_t N>
constexpr ScriptCallbackData(const char(&name)[N], ScriptIdentity _callback) : name(name), index(Utils::hash(name)), callback(_callback) {}
constexpr ScriptCallbackData(const char(&name)[N], CallbackIdentity _callback) : name(name), index(Utils::hash(name)), callback(_callback) {}
};
#endif //TMPTYPES_HPP

View file

@ -1,9 +1,7 @@
//
// Created by koncord on 04.03.17.
//
#include "Utils.hpp"
#include <cstdarg>
using namespace std;
const vector<string> Utils::split(const string &str, int delimiter)
@ -52,3 +50,59 @@ ESM::Cell Utils::getCellFromDescription(std::string cellDescription)
return cell;
}
void Utils::getArguments(std::vector<boost::any> &params, va_list args, const std::string &def)
{
params.reserve(def.length());
try
{
for (char c : def)
{
switch (c)
{
case 'i':
params.emplace_back(va_arg(args, unsigned int));
break;
case 'q':
params.emplace_back(va_arg(args, signed int));
break;
case 'l':
params.emplace_back(va_arg(args, unsigned long long));
break;
case 'w':
params.emplace_back(va_arg(args, signed long long));
break;
case 'f':
params.emplace_back(va_arg(args, double));
break;
case 'p':
params.emplace_back(va_arg(args, void*));
break;
case 's':
params.emplace_back(va_arg(args, const char*));
break;
case 'b':
params.emplace_back(va_arg(args, int));
break;
default:
throw runtime_error("C++ call: Unknown argument identifier " + c);
}
}
}
catch (...)
{
va_end(args);
throw;
}
va_end(args);
}

View file

@ -1,7 +1,3 @@
//
// Created by koncord on 04.03.17.
//
#ifndef OPENMW_UTILS_HPP
#define OPENMW_UTILS_HPP
@ -9,6 +5,8 @@
#include <regex>
#include <vector>
#include <boost/any.hpp>
#include <components/esm/loadcell.hpp>
#include <components/openmw-mp/Utils.hpp>
@ -27,6 +25,8 @@ namespace Utils
ESM::Cell getCellFromDescription(std::string cellDescription);
void getArguments(std::vector<boost::any> &params, va_list args, const std::string &def);
template<size_t N>
constexpr unsigned int hash(const char(&str)[N], size_t I = N)
{

View file

@ -190,48 +190,29 @@ int main(int argc, char *argv[])
LOG_INIT(logLevel);
int players = mgr.getInt("maximumPlayers", "General");
string addr = mgr.getString("localAddress", "General");
string address = mgr.getString("localAddress", "General");
int port = mgr.getInt("port", "General");
string passw = mgr.getString("password", "General");
string password = mgr.getString("password", "General");
string plugin_home = mgr.getString("home", "Plugins");
string moddir = Utils::convertPath(plugin_home + "/data");
string pluginHome = mgr.getString("home", "Plugins");
string dataDirectory = Utils::convertPath(pluginHome + "/data");
vector<string> plugins (Utils::split(mgr.getString("plugins", "Plugins"), ','));
vector<string> plugins(Utils::split(mgr.getString("plugins", "Plugins"), ','));
Utils::printVersion("TES3MP dedicated server", TES3MP_VERSION, version.mCommitHash, TES3MP_PROTO_VERSION);
// Check for unmodified tes3mp-credits file; this makes it so people can't repackage official releases with
// their own made-up credits, though it obviously has no bearing on unofficial releases that change
// the checksum below
boost::filesystem::path folderPath(boost::filesystem::initial_path<boost::filesystem::path>());
folderPath = boost::filesystem::system_complete(boost::filesystem::path(argv[0])).remove_filename();
std::string creditsPath = folderPath.string() + "/tes3mp-credits";
Script::SetModDir(dataDirectory);
unsigned int expectedChecksumInt = Utils::hexStrToInt(TES3MP_CREDITS_CHECKSUM);
bool hasValidCredits = Utils::doesFileHaveChecksum(creditsPath + ".md", expectedChecksumInt);
if (!hasValidCredits)
hasValidCredits = Utils::doesFileHaveChecksum(creditsPath + ".txt", expectedChecksumInt);
if (!hasValidCredits)
{
LOG_MESSAGE_SIMPLE(Log::LOG_FATAL, "The server is shutting down");
LOG_APPEND(Log::LOG_FATAL, "- %s", TES3MP_CREDITS_ERROR);
return 1;
}
setenv("MOD_DIR", moddir.c_str(), 1); // hack for lua
setenv("LUA_PATH", Utils::convertPath(plugin_home + "/scripts/?.lua" + ";"
+ plugin_home + "/scripts/?.t" + ";"
+ plugin_home + "/lib/lua/?.lua" + ";"
+ plugin_home + "/lib/lua/?.t").c_str(), 1);
#ifdef ENABLE_LUA
LangLua::AddPackagePath(Utils::convertPath(pluginHome + "/scripts/?.lua" + ";"
+ pluginHome + "/lib/lua/?.lua" + ";"));
#ifdef _WIN32
setenv("LUA_CPATH", Utils::convertPath(plugin_home + "/lib/?.dll").c_str(), 1);
LangLua::AddPackageCPath(Utils::convertPath(pluginHome + "/lib/?.dll"));
#else
setenv("LUA_CPATH", Utils::convertPath(plugin_home + "/lib/?.so").c_str(), 1);
LangLua::AddPackageCPath(Utils::convertPath(pluginHome + "/lib/?.so"));
#endif
#endif
int code;
@ -245,18 +226,18 @@ int main(int argc, char *argv[])
peer->SetIncomingPassword(sstr.str().c_str(), (int) sstr.str().size());
if (RakNet::NonNumericHostString(addr.c_str()))
if (RakNet::NonNumericHostString(address.c_str()))
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "You cannot use non-numeric addresses for the server.");
return 1;
}
RakNet::SocketDescriptor sd((unsigned short) port, addr.c_str());
RakNet::SocketDescriptor sd((unsigned short) port, address.c_str());
try
{
for (auto plugin : plugins)
Script::LoadScript(plugin.c_str(), plugin_home.c_str());
Script::LoadScript(plugin.c_str(), pluginHome.c_str());
switch (peer->Startup((unsigned) players, &sd, 1))
{
@ -271,7 +252,7 @@ int main(int argc, char *argv[])
case RakNet::SOCKET_FAILED_TO_BIND:
case RakNet::SOCKET_PORT_ALREADY_IN_USE:
case RakNet::PORT_CANNOT_BE_ZERO:
throw runtime_error("Failed to bind port");
throw runtime_error("Failed to bind port. Make sure a server isn't already running on that port.");
case RakNet::SOCKET_FAILED_TEST_SEND:
case RakNet::SOCKET_FAMILY_NOT_SUPPORTED:
case RakNet::FAILED_TO_CREATE_NETWORK_THREAD:
@ -283,7 +264,7 @@ int main(int argc, char *argv[])
peer->SetMaximumIncomingConnections((unsigned short) (players));
Networking networking(peer);
networking.setServerPassword(passw);
networking.setServerPassword(password);
if (mgr.getBool("enabled", "MasterServer"))
{
@ -326,6 +307,7 @@ int main(int argc, char *argv[])
catch (std::exception &e)
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, e.what());
Script::Call<Script::CallbackIdentity("OnServerScriptCrash")>(e.what());
throw; //fall through
}

View file

@ -21,16 +21,7 @@ namespace mwmp
{
DEBUG_PRINTF(strPacketID.c_str());
Script::CallBackReturn<Script::CallbackIdentity("OnPlayerSendMessage")> result = true;
Script::Call<Script::CallbackIdentity("OnPlayerSendMessage")>(result, player.getId(), player.chatMessage.c_str());
if (result)
{
player.chatMessage = player.npc.mName + " (" + std::to_string(player.getId()) + "): "
+ player.chatMessage + "\n";
packet.Send(false);
packet.Send(true);
}
Script::Call<Script::CallbackIdentity("OnPlayerSendMessage")>(player.getId(), player.chatMessage.c_str());
}
};
}

View file

@ -1,7 +1,3 @@
//
// Created by koncord on 31.03.17.
//
#ifndef OPENMW_PROCESSORPLAYERPOSITION_HPP
#define OPENMW_PROCESSORPLAYERPOSITION_HPP
@ -19,11 +15,7 @@ namespace mwmp
void Do(PlayerPacket &packet, Player &player) override
{
//DEBUG_PRINTF(strPacketID);
if (!player.creatureStats.mDead)
{
player.sendToLoaded(&packet);
}
player.sendToLoaded(&packet);
}
};
}

View file

@ -17,8 +17,6 @@ namespace mwmp
{
DEBUG_PRINTF(strPacketID.c_str());
packet.Send(true);
Script::Call<Script::CallbackIdentity("OnWorldKillCount")>(player.getId());
}
};

View file

@ -134,7 +134,7 @@ add_openmw_dir (mwmp/processors/object BaseObjectProcessor
ProcessorScriptGlobalFloat
)
add_openmw_dir (mwmp/processors/worldstate ProcessorCellCreate ProcessorCellReplace ProcessorRecordDynamic
add_openmw_dir (mwmp/processors/worldstate ProcessorCellCreate ProcessorCellReset ProcessorRecordDynamic
ProcessorWorldCollisionOverride ProcessorWorldMap ProcessorWorldRegionAuthority ProcessorWorldTime
ProcessorWorldWeather
)

View file

@ -221,13 +221,13 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
/*
Start of tes3mp addition
Check for unmodified tes3mp-credits file; this makes it so people can't repackage official releases with
their own made-up credits, though it obviously has no bearing on unofficial releases that change
the checksum below
Check for unmodified tes3mp-credits file on Windows; this makes it so people can't repackage official
releases with their own made-up credits, though it obviously has no bearing on unofficial releases that
change the checksum below
*/
boost::filesystem::path folderPath(boost::filesystem::initial_path<boost::filesystem::path>());
folderPath = boost::filesystem::system_complete(boost::filesystem::path(argv[0])).remove_filename();
std::string creditsPath = folderPath.string() + "/tes3mp-credits";
#ifdef _WIN32
std::string creditsPath = (cfgMgr.getLocalPath() / "tes3mp-credits").string();
unsigned int expectedChecksumInt = Utils::hexStrToInt(TES3MP_CREDITS_CHECKSUM);
bool hasValidCredits = Utils::doesFileHaveChecksum(creditsPath + ".md", expectedChecksumInt);
@ -242,6 +242,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "tes3mp", TES3MP_CREDITS_ERROR, 0);
return false;
}
#endif
/*
End of tes3mp addition
*/

View file

@ -553,9 +553,10 @@ namespace MWClass
/*
Start of tes3mp addition
If the attacker was the LocalPlayer or LocalActor, record their target and send a packet with it
If the attacker was the LocalPlayer or LocalActor, record their target and send a
packet with it
If the victim was a LocalActor who died, record their attacker as the deathReason
If the victim was a LocalActor who died, record their attacker as the killer
*/
mwmp::Attack *localAttack = MechanicsHelper::getLocalAttack(attacker);

View file

@ -966,12 +966,13 @@ namespace MWClass
/*
Start of tes3mp addition
If the attacker was the LocalPlayer or LocalActor, record their target and send a packet with it
If the attacker was the LocalPlayer or LocalActor, record their target and send a
packet with it
If the victim was the LocalPlayer, check whether packets should be sent about
their new dynamic stats and position
If the victim was a LocalActor who died, record their attacker as the deathReason
If the victim was a LocalActor who died, record their attacker as the killer
*/
mwmp::Attack *localAttack = MechanicsHelper::getLocalAttack(attacker);

View file

@ -197,14 +197,15 @@ namespace MWGui
/*
Start of tes3mp change (major)
Avoid running any of the original code for dropping items, to prevent possibilities
for item duping or interaction with restricted containers
For valid drops, avoid running the original code for the item transfer, to prevent unilateral
item duping or interaction on this client
Instead, finish the drag in a way that removes the items in it
Instead, finish the drag in a way that removes the items in it, and let the server's reply handle
the rest
*/
//if (success)
// mDragAndDrop->drop(mModel, mItemView);
mDragAndDrop->finish(true);
if (success)
// mDragAndDrop->drop(mModel, mItemView);
mDragAndDrop->finish(true);
/*
End of tes3mp change (major)
*/

View file

@ -306,7 +306,26 @@ namespace MWGui
{
// pick up object
if (!object.isEmpty())
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object);
/*
Start of tes3mp change (major)
Disable unilateral picking up of objects on this client
Instead, send an ID_OBJECT_ACTIVATE packet every time an item is made to pick up
an item here, and expect the server's reply to our packet to cause the actual
picking up of items
*/
//MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object);
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
objectList->addObjectActivate(object, MWMechanics::getPlayer());
objectList->sendObjectActivate();
}
/*
End of tes3mp change (major)
*/
}
}
}

View file

@ -718,21 +718,6 @@ namespace MWGui
// can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object
MWWorld::Ptr newObject = *player.getClass().getContainerStore (player).add (object, object.getRefData().getCount(), player);
/*
Start of tes3mp addition
Send an ID_OBJECT_DELETE packet every time an item from the world is picked up
by the player through the inventory HUD
*/
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
objectList->addObjectDelete(object);
objectList->sendObjectDelete();
/*
End of tes3mp addition
*/
// remove from world
MWBase::Environment::get().getWorld()->deleteObject (object);

View file

@ -164,10 +164,10 @@ namespace MWGui
Start of tes3mp addition
Send a PLAYER_QUICKKEYS packet whenever a key is unassigned, but only if the player
has finished character generation, so as to avoid doing anything doing startup when all
is logged in on the server, so as to avoid doing anything doing at startup when all
quick keys get unassigned by default
*/
if (mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen() && !mwmp::Main::get().getLocalPlayer()->isReceivingQuickKeys)
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() && !mwmp::Main::get().getLocalPlayer()->isReceivingQuickKeys)
{
mwmp::Main::get().getLocalPlayer()->sendQuickKey(key->index, Type_Unassigned);
}

View file

@ -9,6 +9,19 @@
#include <components/misc/rng.hpp>
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp"
#include "../mwmp/MechanicsHelper.hpp"
/*
End of tes3mp addition
*/
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
@ -163,9 +176,26 @@ void Recharge::onItemClicked(MyGUI::Widget *sender, const MWWorld::Ptr& item)
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
item.getClass().getEnchantment(item));
/*
Start of tes3mp change (minor)
Send PlayerInventory packets that replace the original item with the new one
*/
mwmp::LocalPlayer *localPlayer = mwmp::Main::get().getLocalPlayer();
mwmp::Item removedItem = MechanicsHelper::getItem(item, 1);
item.getCellRef().setEnchantmentCharge(
std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast<float>(enchantment->mData.mCharge)));
mwmp::Item addedItem = MechanicsHelper::getItem(item, 1);
localPlayer->sendItemChange(addedItem, mwmp::InventoryChanges::ADD);
localPlayer->sendItemChange(removedItem, mwmp::InventoryChanges::REMOVE);
/*
End of tes3mp change (minor)
*/
MWBase::Environment::get().getWindowManager()->playSound("Enchant Success");
player.getClass().getContainerStore(player).restack(item);

View file

@ -1575,7 +1575,7 @@ namespace MWGui
switch (quickKeyType)
{
case QuickKeysMenu::Type_Unassigned:
mQuickKeysMenu->unassignIndex(slot);
mQuickKeysMenu->unassignIndex(slot - 1);
break;
case QuickKeysMenu::Type_Item:
mQuickKeysMenu->onAssignItem(item);

View file

@ -1070,9 +1070,9 @@ namespace MWInput
/*
Start of tes3mp addition
Ignore attempts to rest if the player has not finished character generation yet
Ignore attempts to rest if the player has not logged in on the server yet
*/
if (!mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen())
if (!mwmp::Main::get().getLocalPlayer()->isLoggedIn())
return;
/*
End of tes3mp addition
@ -1137,6 +1137,17 @@ namespace MWInput
if (MyGUI::InputManager::getInstance ().isModalAny())
return;
/*
Start of tes3mp addition
Ignore attempts to open inventory if the player has not logged in on the server yet
*/
if (!mwmp::Main::get().getLocalPlayer()->isLoggedIn())
return;
/*
End of tes3mp addition
*/
// Toggle between game mode and inventory mode
if(!MWBase::Environment::get().getWindowManager()->isGuiMode())
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Inventory);

View file

@ -324,14 +324,18 @@ namespace MWMechanics
Make it easy to get an effect's duration
*/
float ActiveSpells::getEffectDuration(short effectId)
float ActiveSpells::getEffectDuration(short effectId, std::string sourceId)
{
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
{
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end();)
if (sourceId.compare(it->first) == 0)
{
return effectIt->mDuration;
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end(); ++effectIt)
{
if (effectIt->mEffectId == effectId)
return effectIt->mDuration;
}
}
}
return 0.f;

View file

@ -109,7 +109,7 @@ namespace MWMechanics
Make it easy to get an effect's duration
*/
float getEffectDuration(short effectId);
float getEffectDuration(short effectId, std::string sourceId);
/*
End of tes3mp addition
*/

View file

@ -240,8 +240,22 @@ namespace MWMechanics
// Set the soul on just one of the gems, not the whole stack
gem->getContainerStore()->unstack(*gem, caster);
/*
Start of tes3mp change (minor)
Send PlayerInventory packets that replace the original gem with the new one
*/
mwmp::LocalPlayer *localPlayer = mwmp::Main::get().getLocalPlayer();
localPlayer->sendItemChange(*gem, 1, mwmp::InventoryChanges::REMOVE);
gem->getCellRef().setSoul(mCreature.getCellRef().getRefId());
localPlayer->sendItemChange(*gem, 1, mwmp::InventoryChanges::ADD);
/*
End of tes3mp change (minor)
*/
// Restack the gem with other gems with the same soul
gem->getContainerStore()->restack(*gem);
@ -828,6 +842,25 @@ namespace MWMechanics
if (isDamageEffect)
{
/*
Start of tes3mp addition
If the victim was the LocalPlayer or a LocalActor, record the caster as their killer
*/
mwmp::Target killer = MechanicsHelper::getTarget(caster);
if (ptr == MWMechanics::getPlayer())
{
mwmp::Main::get().getLocalPlayer()->killer = killer;
}
else if (mwmp::Main::get().getCellController()->isLocalActor(ptr))
{
mwmp::Main::get().getCellController()->getLocalActor(ptr)->killer = killer;
}
/*
End of tes3mp addition
*/
if (caster == player || playerFollowers.find(caster) != playerFollowers.end())
{
if (caster.getClass().getNpcStats(caster).isWerewolf())

View file

@ -578,7 +578,7 @@ namespace MWMechanics
if (localAttack && localAttack->pressed != true)
{
MechanicsHelper::resetAttack(localAttack);
localAttack->type = distantCombat ? mwmp::Attack::MELEE : mwmp::Attack::RANGED;
localAttack->type = distantCombat ? mwmp::Attack::RANGED : mwmp::Attack::MELEE;
localAttack->pressed = true;
localAttack->shouldSend = true;
}

View file

@ -4,6 +4,19 @@
#include <components/misc/rng.hpp>
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp"
#include "../mwmp/MechanicsHelper.hpp"
/*
End of tes3mp addition
*/
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
@ -57,8 +70,25 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
// repair by 'y' points
int charge = itemToRepair.getClass().getItemHealth(itemToRepair);
charge = std::min(charge + y, itemToRepair.getClass().getItemMaxHealth(itemToRepair));
/*
Start of tes3mp change (minor)
Send PlayerInventory packets that replace the original item with the new one
*/
mwmp::LocalPlayer *localPlayer = mwmp::Main::get().getLocalPlayer();
mwmp::Item removedItem = MechanicsHelper::getItem(itemToRepair, 1);
itemToRepair.getCellRef().setCharge(charge);
mwmp::Item addedItem = MechanicsHelper::getItem(itemToRepair, 1);
localPlayer->sendItemChange(addedItem, mwmp::InventoryChanges::ADD);
localPlayer->sendItemChange(removedItem, mwmp::InventoryChanges::REMOVE);
/*
End of tes3mp change (minor)
*/
// attempt to re-stack item, in case it was fully repaired
MWWorld::ContainerStoreIterator stacked = player.getClass().getContainerStore(player).restack(itemToRepair);

View file

@ -537,7 +537,7 @@ namespace MWMechanics
/*
Start of tes3mp addition
If the victim was a LocalPlayer or LocalActor who died, record their attacker as the deathReason
If the victim was a LocalPlayer or LocalActor who died, record the caster as the killer
*/
if (!wasDead && isDead)
{

View file

@ -111,8 +111,8 @@ namespace MWMechanics
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
MWMechanics::CreatureStats *actorCreatureStats = &mActor.getClass().getCreatureStats(mActor);
objectList->addObjectSpawn(placed, mActor, actorCreatureStats->getActiveSpells().getEffectDuration(it->first));
float duration = actorCreatureStats->getActiveSpells().getEffectDuration(it->first, it->second);
objectList->addObjectSpawn(placed, mActor, duration);
objectList->sendObjectSpawn();
}

View file

@ -82,8 +82,10 @@ void Cell::updateLocal(bool forceUpdate)
}
else
{
// Forcibly update this local actor if its data has never been sent before;
// otherwise, use the current forceUpdate value
if (actor->getPtr().getRefData().isEnabled())
actor->update(forceUpdate);
actor->update(actor->hasSentData ? forceUpdate : true);
++it;
}
@ -299,19 +301,23 @@ void Cell::readAttack(ActorList& actorList)
if (dedicatedActors.count(mapIndex) > 0)
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Reading ActorAttack about %s", mapIndex.c_str());
DedicatedActor *actor = dedicatedActors[mapIndex];
actor->attack = baseActor.attack;
// Set the correct drawState here if we've somehow we've missed a previous
// AnimFlags packet
if (actor->drawState != 1 && (actor->attack.type == mwmp::Attack::MELEE || actor->attack.type == mwmp::Attack::RANGED))
if (actor->drawState != MWMechanics::DrawState_::DrawState_Weapon &&
(actor->attack.type == mwmp::Attack::MELEE || actor->attack.type == mwmp::Attack::RANGED))
{
actor->drawState = 1;
actor->drawState = MWMechanics::DrawState_::DrawState_Weapon;
actor->setAnimFlags();
}
else if (actor->drawState != 2 && (actor->attack.type == mwmp::Attack::MAGIC || actor->attack.type == mwmp::Attack::ITEM_MAGIC))
else if (actor->drawState != MWMechanics::DrawState_::DrawState_Spell &&
(actor->attack.type == mwmp::Attack::MAGIC || actor->attack.type == mwmp::Attack::ITEM_MAGIC))
{
actor->drawState = 2;
actor->drawState = MWMechanics::DrawState_::DrawState_Spell;
actor->setAnimFlags();
}
@ -397,6 +403,9 @@ void Cell::readCellChange(ActorList& actorList)
void Cell::initializeLocalActor(const MWWorld::Ptr& ptr)
{
std::string mapIndex = Main::get().getCellController()->generateMapIndex(ptr);
LOG_APPEND(Log::LOG_VERBOSE, "- Initializing LocalActor %s in %s", mapIndex.c_str(), getDescription().c_str());
LocalActor *actor = new LocalActor();
actor->cell = *store->getCell();
actor->setPtr(ptr);
@ -406,16 +415,17 @@ void Cell::initializeLocalActor(const MWWorld::Ptr& ptr)
if (ptr.getClass().getCreatureStats(ptr).isDead())
actor->wasDead = true;
std::string mapIndex = Main::get().getCellController()->generateMapIndex(ptr);
localActors[mapIndex] = actor;
Main::get().getCellController()->setLocalActorRecord(mapIndex, getDescription());
LOG_APPEND(Log::LOG_VERBOSE, "- Initialized LocalActor %s in %s", mapIndex.c_str(), getDescription().c_str());
LOG_APPEND(Log::LOG_VERBOSE, "- Successfully initialized LocalActor %s in %s", mapIndex.c_str(), getDescription().c_str());
}
void Cell::initializeLocalActors()
{
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Initializing LocalActors in %s", getDescription().c_str());
for (const auto &mergedRef : store->getMergedRefs())
{
if (mergedRef->mClass->isActor())
@ -432,20 +442,24 @@ void Cell::initializeLocalActors()
initializeLocalActor(ptr);
}
}
LOG_APPEND(Log::LOG_VERBOSE, "- Successfully initialized LocalActors in %s", getDescription().c_str());
}
void Cell::initializeDedicatedActor(const MWWorld::Ptr& ptr)
{
std::string mapIndex = Main::get().getCellController()->generateMapIndex(ptr);
LOG_APPEND(Log::LOG_VERBOSE, "- Initializing DedicatedActor %s in %s", mapIndex.c_str(), getDescription().c_str());
DedicatedActor *actor = new DedicatedActor();
actor->cell = *store->getCell();
actor->setPtr(ptr);
std::string mapIndex = Main::get().getCellController()->generateMapIndex(ptr);
dedicatedActors[mapIndex] = actor;
Main::get().getCellController()->setDedicatedActorRecord(mapIndex, getDescription());
LOG_APPEND(Log::LOG_VERBOSE, "- Initialized DedicatedActor %s in %s", mapIndex.c_str(), getDescription().c_str());
LOG_APPEND(Log::LOG_VERBOSE, "- Successfully initialized DedicatedActor %s in %s", mapIndex.c_str(), getDescription().c_str());
}
void Cell::initializeDedicatedActors(ActorList& actorList)

View file

@ -77,6 +77,8 @@ void CellController::initializeCell(const ESM::Cell& cell)
// If this key doesn't exist, create it
if (cellsInitialized.count(mapIndex) == 0)
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Initializing mwmp::Cell %s", cell.getDescription().c_str());
MWWorld::CellStore *cellStore = getCellStore(cell);
if (!cellStore) return;
@ -84,7 +86,7 @@ void CellController::initializeCell(const ESM::Cell& cell)
mwmp::Cell *mpCell = new mwmp::Cell(cellStore);
cellsInitialized[mapIndex] = mpCell;
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "- Initialized mwmp::Cell %s", mpCell->getDescription().c_str());
LOG_APPEND(Log::LOG_VERBOSE, "- Successfully initialized mwmp::Cell %s", cell.getDescription().c_str());
}
}

View file

@ -35,7 +35,7 @@ using namespace std;
DedicatedActor::DedicatedActor()
{
drawState = 0;
drawState = MWMechanics::DrawState_::DrawState_Nothing;
movementFlags = 0;
animation.groupname = "";
sound = "";
@ -133,12 +133,7 @@ void DedicatedActor::setAnimFlags()
MWMechanics::CreatureStats *ptrCreatureStats = &ptr.getClass().getCreatureStats(ptr);
if (drawState == 0)
ptrCreatureStats->setDrawState(DrawState_Nothing);
else if (drawState == 1)
ptrCreatureStats->setDrawState(DrawState_Weapon);
else if (drawState == 2)
ptrCreatureStats->setDrawState(DrawState_Spell);
ptrCreatureStats->setDrawState(static_cast<MWMechanics::DrawState_>(drawState));
ptrCreatureStats->setMovementFlag(CreatureStats::Flag_Run, (movementFlags & CreatureStats::Flag_Run) != 0);
ptrCreatureStats->setMovementFlag(CreatureStats::Flag_Sneak, (movementFlags & CreatureStats::Flag_Sneak) != 0);

View file

@ -71,6 +71,13 @@ DedicatedPlayer::~DedicatedPlayer()
void DedicatedPlayer::update(float dt)
{
// Only move and set anim flags if the framerate isn't too low
if (dt < 0.1)
{
move(dt);
setAnimFlags();
}
MWMechanics::CreatureStats *ptrCreatureStats = &ptr.getClass().getCreatureStats(ptr);
MWMechanics::DynamicStat<float> value;
@ -100,13 +107,6 @@ void DedicatedPlayer::update(float dt)
ptrCreatureStats->setAiSetting(MWMechanics::CreatureStats::AI_Fight, 0);
ptrCreatureStats->setAiSetting(MWMechanics::CreatureStats::AI_Flee, 0);
ptrCreatureStats->setAiSetting(MWMechanics::CreatureStats::AI_Hello, 0);
// Only move and set anim flags if the framerate isn't too low
if (dt < 0.1)
{
move(dt);
setAnimFlags();
}
}
void DedicatedPlayer::move(float dt)
@ -258,14 +258,10 @@ void DedicatedPlayer::setAnimFlags()
cast.cast("Levitate");
}
if (drawState == 0)
ptr.getClass().getCreatureStats(ptr).setDrawState(DrawState_Nothing);
else if (drawState == 1)
ptr.getClass().getCreatureStats(ptr).setDrawState(DrawState_Weapon);
else if (drawState == 2)
ptr.getClass().getCreatureStats(ptr).setDrawState(DrawState_Spell);
MWMechanics::CreatureStats *ptrCreatureStats = &ptr.getClass().getCreatureStats(ptr);
ptrCreatureStats->setDrawState(static_cast<MWMechanics::DrawState_>(drawState));
ptrCreatureStats->setMovementFlag(CreatureStats::Flag_Run, (movementFlags & CreatureStats::Flag_Run) != 0);
ptrCreatureStats->setMovementFlag(CreatureStats::Flag_Sneak, (movementFlags & CreatureStats::Flag_Sneak) != 0);
ptrCreatureStats->setMovementFlag(CreatureStats::Flag_ForceJump, (movementFlags & CreatureStats::Flag_ForceJump) != 0);
@ -388,7 +384,9 @@ void DedicatedPlayer::setCell()
removeMarker();
// Otherwise, update their marker so the player shows up in the right cell on the world map
else
updateMarker();
{
enableMarker();
}
// If this player is now in a cell that we are the local authority over, we should send them all
// NPC data in that cell
@ -412,7 +410,9 @@ void DedicatedPlayer::setCell()
void DedicatedPlayer::updateMarker()
{
if (!markerEnabled)
{
return;
}
GUIController *gui = Main::get().getGUIController();
@ -423,7 +423,15 @@ void DedicatedPlayer::updateMarker()
gui->mPlayerMarkers.addMarker(marker);
}
else
{
gui->mPlayerMarkers.addMarker(marker, true);
}
}
void DedicatedPlayer::enableMarker()
{
markerEnabled = true;
updateMarker();
}
void DedicatedPlayer::removeMarker()
@ -435,18 +443,9 @@ void DedicatedPlayer::removeMarker()
GUIController *gui = Main::get().getGUIController();
if (gui->mPlayerMarkers.contains(marker))
Main::get().getGUIController()->mPlayerMarkers.deleteMarker(marker);
}
void DedicatedPlayer::setMarkerState(bool state)
{
if (state)
{
markerEnabled = true;
updateMarker();
Main::get().getGUIController()->mPlayerMarkers.deleteMarker(marker);
}
else
removeMarker();
}
void DedicatedPlayer::playAnimation()
@ -476,7 +475,7 @@ void DedicatedPlayer::createReference(const std::string& recId)
ESM::CustomMarker mEditingMarker = Main::get().getGUIController()->createMarker(guid);
marker = mEditingMarker;
setMarkerState(true);
enableMarker();
}
void DedicatedPlayer::deleteReference()

View file

@ -47,7 +47,7 @@ namespace mwmp
void updateMarker();
void removeMarker();
void setMarkerState(bool state);
void enableMarker();
void playAnimation();
void playSpeech();

View file

@ -162,9 +162,9 @@ namespace mwmp
windowState++;
if (windowState == 3) windowState = 0;
std::string chatMode = windowState == CHAT_DISABLED ? "Chat disabled" :
windowState == CHAT_ENABLED ? "Chat enabled" :
"Chat in hidden mode";
std::string chatMode = windowState == CHAT_DISABLED ? "Chat hidden" :
windowState == CHAT_ENABLED ? "Chat visible" :
"Chat appearing when needed";
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Switch chat mode to %s", chatMode.c_str());
MWBase::Environment::get().getWindowManager()->messageBox(chatMode);

View file

@ -22,6 +22,7 @@ using namespace std;
LocalActor::LocalActor()
{
hasSentData = false;
posWasChanged = false;
equipmentChanged = false;
@ -61,14 +62,16 @@ void LocalActor::update(bool forceUpdate)
updateSpeech();
updateAttack();
}
hasSentData = true;
}
void LocalActor::updateCell()
{
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Sending ID_ACTOR_CELL_CHANGE about %s %i-%i to server",
refId.c_str(), refNum, mpNum);
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Sending ID_ACTOR_CELL_CHANGE about %s %i-%i in cell %s to server",
refId.c_str(), refNum, mpNum, cell.getDescription().c_str());
LOG_APPEND(Log::LOG_VERBOSE, "- Moved from %s to %s", cell.getDescription().c_str(), ptr.getCell()->getCell()->getDescription().c_str());
LOG_APPEND(Log::LOG_VERBOSE, "- Moved to cell %s", ptr.getCell()->getCell()->getDescription().c_str());
cell = *ptr.getCell()->getCell();
position = ptr.getRefData().getPosition();
@ -191,8 +194,8 @@ void LocalActor::updateStatsDynamic(bool forceUpdate)
if (MechanicsHelper::isEmptyTarget(killer))
killer = MechanicsHelper::getTarget(ptr);
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_ACTOR_DEATH about %s %i-%i to server",
refId.c_str(), refNum, mpNum);
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_ACTOR_DEATH about %s %i-%i in cell %s to server",
refId.c_str(), refNum, mpNum, cell.getDescription().c_str());
mwmp::Main::get().getNetworking()->getActorList()->addDeathActor(*this);

View file

@ -28,6 +28,7 @@ namespace mwmp
MWWorld::Ptr getPtr();
void setPtr(const MWWorld::Ptr& newPtr);
bool hasSentData;
bool wasDead;
private:

View file

@ -36,6 +36,7 @@
#include "LocalPlayer.hpp"
#include "Main.hpp"
#include "Networking.hpp"
#include "PlayerList.hpp"
#include "CellController.hpp"
#include "GUIController.hpp"
#include "MechanicsHelper.hpp"
@ -46,6 +47,7 @@ using namespace std;
LocalPlayer::LocalPlayer()
{
deathTime = time(0);
receivedCharacter = false;
charGenState.currentStage = 0;
charGenState.endStage = 1;
@ -193,9 +195,12 @@ bool LocalPlayer::processCharGen()
return true;
}
bool LocalPlayer::hasFinishedCharGen()
bool LocalPlayer::isLoggedIn()
{
return charGenState.isFinished;
if (charGenState.isFinished && (charGenState.endStage > 1 || receivedCharacter))
return true;
return false;
}
void LocalPlayer::updateStatsDynamic(bool forceUpdate)
@ -221,16 +226,16 @@ void LocalPlayer::updateStatsDynamic(bool forceUpdate)
|| abs(oldVal.getCurrent() - newVal.getCurrent()) >= limit);
};
if (needUpdate(oldHealth, health, 2))
if (forceUpdate || needUpdate(oldHealth, health, 2))
statsDynamicIndexChanges.push_back(0);
if (needUpdate(oldMagicka, magicka, 4))
if (forceUpdate || needUpdate(oldMagicka, magicka, 4))
statsDynamicIndexChanges.push_back(1);
if (needUpdate(oldFatigue, fatigue, 4))
if (forceUpdate || needUpdate(oldFatigue, fatigue, 4))
statsDynamicIndexChanges.push_back(2);
if (statsDynamicIndexChanges.size() > 0 || forceUpdate)
if (forceUpdate || statsDynamicIndexChanges.size() > 0)
{
oldHealth = health;
oldMagicka = magicka;
@ -280,6 +285,7 @@ void LocalPlayer::updateAttributes(bool forceUpdate)
{
if (ptrNpcStats.getAttribute(i).getBase() != creatureStats.mAttributes[i].mBase ||
ptrNpcStats.getAttribute(i).getModifier() != creatureStats.mAttributes[i].mMod ||
ptrNpcStats.getAttribute(i).getDamage() != creatureStats.mAttributes[i].mDamage ||
ptrNpcStats.getSkillIncrease(i) != npcStats.mSkillIncrease[i] ||
forceUpdate)
{
@ -314,6 +320,7 @@ void LocalPlayer::updateSkills(bool forceUpdate)
// Update a skill if its base value has changed at all or its progress has changed enough
if (ptrNpcStats.getSkill(i).getBase() != npcStats.mSkills[i].mBase ||
ptrNpcStats.getSkill(i).getModifier() != npcStats.mSkills[i].mMod ||
ptrNpcStats.getSkill(i).getDamage() != npcStats.mSkills[i].mDamage ||
abs(ptrNpcStats.getSkill(i).getProgress() - npcStats.mSkills[i].mProgress) > 0.75 ||
forceUpdate)
{
@ -457,6 +464,13 @@ void LocalPlayer::updateCell(bool forceUpdate)
getNetworking()->getPlayerPacket(ID_PLAYER_CELL_CHANGE)->Send();
isChangingRegion = false;
// If this is an interior cell, are there any other players in it? If so,
// enable their markers
if (!ptrCell->isExterior())
{
mwmp::PlayerList::enableMarkers(*ptrCell);
}
}
}
@ -623,12 +637,12 @@ void LocalPlayer::updateAnimFlags(bool forceUpdate)
static bool wasJumping = false;
static bool wasFlying = false;
MWMechanics::DrawState_ currentDrawState = ptrPlayer.getClass().getNpcStats(ptrPlayer).getDrawState();
static MWMechanics::DrawState_ lastDrawState = ptrPlayer.getClass().getNpcStats(ptrPlayer).getDrawState();
drawState = ptrPlayer.getClass().getNpcStats(ptrPlayer).getDrawState();
static char lastDrawState = ptrPlayer.getClass().getNpcStats(ptrPlayer).getDrawState();
if (wasRunning != isRunning ||
wasSneaking != isSneaking || wasForceJumping != isForceJumping ||
wasForceMoveJumping != isForceMoveJumping || lastDrawState != currentDrawState ||
wasForceMoveJumping != isForceMoveJumping || lastDrawState != drawState ||
wasJumping || isJumping || wasFlying != isFlying ||
forceUpdate)
{
@ -636,7 +650,7 @@ void LocalPlayer::updateAnimFlags(bool forceUpdate)
wasRunning = isRunning;
wasForceJumping = isForceJumping;
wasForceMoveJumping = isForceMoveJumping;
lastDrawState = currentDrawState;
lastDrawState = drawState;
wasFlying = isFlying;
wasJumping = isJumping;
@ -653,13 +667,6 @@ void LocalPlayer::updateAnimFlags(bool forceUpdate)
#undef __SETFLAG
if (currentDrawState == MWMechanics::DrawState_Nothing)
drawState = 0;
else if (currentDrawState == MWMechanics::DrawState_Weapon)
drawState = 1;
else if (currentDrawState == MWMechanics::DrawState_Spell)
drawState = 2;
if (isJumping)
updatePosition(true); // fix position after jump;
@ -671,6 +678,7 @@ void LocalPlayer::updateAnimFlags(bool forceUpdate)
void LocalPlayer::addItems()
{
MWWorld::Ptr ptrPlayer = getPlayerPtr();
const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore();
MWWorld::ContainerStore &ptrStore = ptrPlayer.getClass().getContainerStore(ptrPlayer);
for (const auto &item : inventoryChanges.items)
@ -681,7 +689,9 @@ void LocalPlayer::addItems()
try
{
MWWorld::Ptr itemPtr = *ptrStore.add(item.refId, item.count, ptrPlayer);
MWWorld::ManualRef itemRef(esmStore, item.refId, item.count);
MWWorld::Ptr itemPtr = itemRef.getPtr();
if (item.charge != -1)
itemPtr.getCellRef().setCharge(item.charge);
@ -690,6 +700,8 @@ void LocalPlayer::addItems()
if (!item.soul.empty())
itemPtr.getCellRef().setSoul(item.soul);
ptrStore.add(itemPtr, item.count, ptrPlayer);
}
catch (std::exception&)
{
@ -697,7 +709,7 @@ void LocalPlayer::addItems()
}
}
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView();
updateInventoryWindow();
}
void LocalPlayer::addSpells()
@ -832,11 +844,11 @@ void LocalPlayer::resurrect()
// Ensure we unequip any items with constant effects that can put us into an infinite
// death loop
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::DrainHealth);
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::FireDamage);
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::FrostDamage);
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::ShockDamage);
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::SunDamage);
static const int damageEffects[5] = { ESM::MagicEffect::DrainHealth, ESM::MagicEffect::FireDamage,
ESM::MagicEffect::FrostDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::SunDamage };
for (const auto &damageEffect : damageEffects)
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, damageEffect);
Main::get().getNetworking()->getPlayerPacket(ID_PLAYER_RESURRECT)->setPlayer(this);
Main::get().getNetworking()->getPlayerPacket(ID_PLAYER_RESURRECT)->Send();
@ -853,8 +865,15 @@ void LocalPlayer::closeInventoryWindows()
MWBase::Environment::get().getWindowManager()->finishDragDrop();
}
void LocalPlayer::updateInventoryWindow()
{
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView();
}
void LocalPlayer::setCharacter()
{
receivedCharacter = true;
MWBase::World *world = MWBase::Environment::get().getWorld();
// Ignore invalid races
@ -909,7 +928,7 @@ void LocalPlayer::setAttributes()
{
MWWorld::Ptr ptrPlayer = getPlayerPtr();
MWMechanics::CreatureStats *ptrCreatureStats = &ptrPlayer.getClass().getCreatureStats(ptrPlayer);
MWMechanics::NpcStats *ptrNpcStats = &ptrPlayer.getClass().getNpcStats(ptrPlayer);
MWMechanics::AttributeValue attributeValue;
for (int attributeIndex = 0; attributeIndex < 8; ++attributeIndex)
@ -917,14 +936,14 @@ void LocalPlayer::setAttributes()
// If the server wants to clear our attribute's non-zero modifier, we need to remove
// the spell effect causing it, to avoid an infinite loop where the effect keeps resetting
// the modifier
if (creatureStats.mAttributes[attributeIndex].mMod == 0 && ptrCreatureStats->getAttribute(attributeIndex).getModifier() > 0)
if (creatureStats.mAttributes[attributeIndex].mMod == 0 && ptrNpcStats->getAttribute(attributeIndex).getModifier() > 0)
{
ptrCreatureStats->getActiveSpells().purgeEffectByArg(ESM::MagicEffect::FortifyAttribute, attributeIndex);
ptrNpcStats->getActiveSpells().purgeEffectByArg(ESM::MagicEffect::FortifyAttribute, attributeIndex);
MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(ptrPlayer);
// Is the modifier for this attribute still higher than 0? If so, unequip items that
// fortify the attribute
if (ptrCreatureStats->getAttribute(attributeIndex).getModifier() > 0)
if (ptrNpcStats->getAttribute(attributeIndex).getModifier() > 0)
{
MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::FortifyAttribute, attributeIndex, -1);
mwmp::Main::get().getGUIController()->refreshGuiMode(MWGui::GM_Inventory);
@ -932,7 +951,9 @@ void LocalPlayer::setAttributes()
}
attributeValue.readState(creatureStats.mAttributes[attributeIndex]);
ptrCreatureStats->setAttribute(attributeIndex, attributeValue);
ptrNpcStats->setAttribute(attributeIndex, attributeValue);
ptrNpcStats->setSkillIncrease(attributeIndex, npcStats.mSkillIncrease[attributeIndex]);
}
}
@ -965,11 +986,6 @@ void LocalPlayer::setSkills()
skillValue.readState(npcStats.mSkills[skillIndex]);
ptrNpcStats->setSkill(skillIndex, skillValue);
}
for (int attributeIndex = 0; attributeIndex < 8; ++attributeIndex)
ptrNpcStats->setSkillIncrease(attributeIndex, npcStats.mSkillIncrease[attributeIndex]);
ptrNpcStats->setLevelProgress(npcStats.mLevelProgress);
}
void LocalPlayer::setLevel()
@ -977,8 +993,9 @@ void LocalPlayer::setLevel()
MWBase::World *world = MWBase::Environment::get().getWorld();
MWWorld::Ptr ptrPlayer = world->getPlayerPtr();
MWMechanics::CreatureStats *ptrCreatureStats = &ptrPlayer.getClass().getCreatureStats(ptrPlayer);
ptrCreatureStats->setLevel(creatureStats.mLevel);
MWMechanics::NpcStats *ptrNpcStats = &ptrPlayer.getClass().getNpcStats(ptrPlayer);
ptrNpcStats->setLevel(creatureStats.mLevel);
ptrNpcStats->setLevelProgress(npcStats.mLevelProgress);
}
void LocalPlayer::setBounty()
@ -1294,8 +1311,23 @@ void LocalPlayer::setFactions()
void LocalPlayer::setKills()
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Received ID_WORLD_KILL_COUNT with the following kill counts:");
std::string debugMessage = "";
for (const auto &kill : killChanges.kills)
{
if (Log::GetLevel() <= Log::LOG_INFO)
{
if (!debugMessage.empty())
debugMessage += ", ";
debugMessage += kill.refId + ": " + std::to_string(kill.number);
}
MWBase::Environment::get().getMechanicsManager()->setDeaths(kill.refId, kill.number);
}
LOG_APPEND(Log::LOG_INFO, "- %s", debugMessage.c_str());
}
void LocalPlayer::setBooks()
@ -1359,7 +1391,7 @@ void LocalPlayer::sendClass()
void LocalPlayer::sendInventory()
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending entire inventory to server");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending entire inventory to server");
MWWorld::Ptr ptrPlayer = getPlayerPtr();
MWWorld::InventoryStore &ptrInventory = ptrPlayer.getClass().getInventoryStore(ptrPlayer);
@ -1392,36 +1424,28 @@ void LocalPlayer::sendInventory()
getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->Send();
}
void LocalPlayer::sendItemChange(const MWWorld::Ptr& itemPtr, int count, unsigned int action)
void LocalPlayer::sendItemChange(const mwmp::Item& item, unsigned int action)
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending item change for %s with action %i, count %i",
itemPtr.getCellRef().getRefId().c_str(), action, count);
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending item change for %s with action %i, count %i",
item.refId.c_str(), action, item.count);
inventoryChanges.items.clear();
mwmp::Item item;
if (itemPtr.getClass().isGold(itemPtr))
item.refId = MWWorld::ContainerStore::sGoldId;
else
item.refId = itemPtr.getCellRef().getRefId();
item.count = count;
item.charge = itemPtr.getCellRef().getCharge();
item.enchantmentCharge = itemPtr.getCellRef().getEnchantmentCharge();
item.soul = itemPtr.getCellRef().getSoul();
inventoryChanges.items.push_back(item);
inventoryChanges.action = action;
getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->setPlayer(this);
getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->Send();
}
void LocalPlayer::sendItemChange(const MWWorld::Ptr& itemPtr, int count, unsigned int action)
{
mwmp::Item item = MechanicsHelper::getItem(itemPtr, count);
sendItemChange(item, action);
}
void LocalPlayer::sendItemChange(const std::string& refId, int count, unsigned int action)
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending item change for %s with action %i, count %i",
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending item change for %s with action %i, count %i",
refId.c_str(), action, count);
inventoryChanges.items.clear();

View file

@ -16,6 +16,7 @@ namespace mwmp
virtual ~LocalPlayer();
time_t deathTime;
bool receivedCharacter;
bool isReceivingInventory;
bool isReceivingQuickKeys;
@ -25,7 +26,7 @@ namespace mwmp
void update();
bool processCharGen();
bool hasFinishedCharGen();
bool isLoggedIn();
void updateStatsDynamic(bool forceUpdate = false);
void updateAttributes(bool forceUpdate = false);
@ -51,6 +52,7 @@ namespace mwmp
void resurrect();
void closeInventoryWindows();
void updateInventoryWindow();
void setCharacter();
void setDynamicStats();
@ -76,6 +78,7 @@ namespace mwmp
void sendClass();
void sendInventory();
void sendItemChange(const mwmp::Item& item, unsigned int action);
void sendItemChange(const MWWorld::Ptr& itemPtr, int count, unsigned int action);
void sendItemChange(const std::string& refId, int count, unsigned int action);
void sendSpellbook();

View file

@ -51,8 +51,8 @@ using namespace mwmp;
using namespace std;
Main *Main::pMain = 0;
std::string Main::addr = "";
std::string Main::passw = TES3MP_DEFAULT_PASSW;
std::string Main::address = "";
std::string Main::serverPassword = TES3MP_DEFAULT_PASSW;
std::string Main::resourceDir = "";
std::string Main::getResDir()
@ -60,7 +60,7 @@ std::string Main::getResDir()
return resourceDir;
}
std::string loadSettings (Settings::Manager & settings)
std::string loadSettings(Settings::Manager& settings)
{
Files::ConfigurationManager mCfgMgr;
// Create the settings manager and load default settings file
@ -90,7 +90,6 @@ Main::Main()
mLocalPlayer = new LocalPlayer();
mGUIController = new GUIController();
mCellController = new CellController();
//mLocalPlayer->CharGen(0, 4);
server = "mp.tes3mp.com";
port = 25565;
@ -118,8 +117,8 @@ void Main::optionsDesc(boost::program_options::options_description *desc)
void Main::configure(const boost::program_options::variables_map &variables)
{
Main::addr = variables["connect"].as<string>();
Main::passw = variables["password"].as<string>();
Main::address = variables["connect"].as<string>();
Main::serverPassword = variables["password"].as<string>();
resourceDir = variables["resources"].as<Files::EscapeHashString>().toStdString();
}
@ -127,22 +126,22 @@ static Settings::CategorySettingValueMap saveUserSettings;
static Settings::CategorySettingValueMap saveDefaultSettings;
static Settings::CategorySettingVector saveChangedSettings;
void InitMgr(Settings::Manager &mgr)
void initializeManager(Settings::Manager &manager)
{
saveUserSettings = mgr.mUserSettings;
saveDefaultSettings = mgr.mDefaultSettings;
saveChangedSettings = mgr.mChangedSettings;
mgr.mUserSettings.clear();
mgr.mDefaultSettings.clear();
mgr.mChangedSettings.clear();
loadSettings(mgr);
saveUserSettings = manager.mUserSettings;
saveDefaultSettings = manager.mDefaultSettings;
saveChangedSettings = manager.mChangedSettings;
manager.mUserSettings.clear();
manager.mDefaultSettings.clear();
manager.mChangedSettings.clear();
loadSettings(manager);
}
void RestoreMgr(Settings::Manager &mgr)
void restoreManager(Settings::Manager &manager)
{
mgr.mUserSettings = saveUserSettings;
mgr.mDefaultSettings = saveDefaultSettings;
mgr.mChangedSettings = saveChangedSettings;
manager.mUserSettings = saveUserSettings;
manager.mDefaultSettings = saveDefaultSettings;
manager.mChangedSettings = saveChangedSettings;
}
bool Main::init(std::vector<std::string> &content, Files::Collections &collections)
@ -150,40 +149,40 @@ bool Main::init(std::vector<std::string> &content, Files::Collections &collectio
assert(!pMain);
pMain = new Main();
Settings::Manager mgr;
InitMgr(mgr);
Settings::Manager manager;
initializeManager(manager);
int logLevel = mgr.getInt("logLevel", "General");
int logLevel = manager.getInt("logLevel", "General");
Log::SetLevel(logLevel);
if (addr.empty())
if (address.empty())
{
pMain->server = mgr.getString("destinationAddress", "General");
pMain->port = (unsigned short) mgr.getInt("port", "General");
pMain->server = manager.getString("destinationAddress", "General");
pMain->port = (unsigned short) manager.getInt("port", "General");
passw = mgr.getString("password", "General");
if (passw.empty())
passw = TES3MP_DEFAULT_PASSW;
serverPassword = manager.getString("password", "General");
if (serverPassword.empty())
serverPassword = TES3MP_DEFAULT_PASSW;
}
else
{
size_t delim_pos = addr.find(':');
pMain->server = addr.substr(0, delim_pos);
pMain->port = atoi(addr.substr(delim_pos + 1).c_str());
size_t delimPos = address.find(':');
pMain->server = address.substr(0, delimPos);
pMain->port = atoi(address.substr(delimPos + 1).c_str());
}
get().mLocalPlayer->passw = passw;
get().mLocalPlayer->serverPassword = serverPassword;
pMain->mNetworking->connect(pMain->server, pMain->port, content, collections);
RestoreMgr(mgr);
restoreManager(manager);
return pMain->mNetworking->isConnected();
}
void Main::postInit()
{
Settings::Manager mgr;
InitMgr(mgr);
Settings::Manager manager;
initializeManager(manager);
pMain->mGUIController->setupChat(mgr);
RestoreMgr(mgr);
pMain->mGUIController->setupChat(manager);
restoreManager(manager);
const MWBase::Environment &environment = MWBase::Environment::get();
environment.getStateManager()->newGame(true);

View file

@ -39,8 +39,8 @@ namespace mwmp
private:
static std::string resourceDir;
static std::string addr;
static std::string passw;
static std::string address;
static std::string serverPassword;
Main (const Main&);
///< not implemented
Main& operator= (const Main&);

View file

@ -83,7 +83,7 @@ bool MechanicsHelper::isUsingRangedWeapon(const MWWorld::Ptr& ptr)
MWWorld::ContainerStoreIterator weaponSlot = inventoryStore.getSlot(
MWWorld::InventoryStore::Slot_CarriedRight);
if (weaponSlot != inventoryStore.end())
if (weaponSlot != inventoryStore.end() && weaponSlot->getTypeName() == typeid(ESM::Weapon).name())
{
const ESM::Weapon* weaponRecord = weaponSlot->get<ESM::Weapon>()->mBase;
@ -125,6 +125,23 @@ MWWorld::Ptr MechanicsHelper::getPlayerPtr(const Target& target)
return nullptr;
}
mwmp::Item MechanicsHelper::getItem(const MWWorld::Ptr& itemPtr, int count)
{
mwmp::Item item;
if (itemPtr.getClass().isGold(itemPtr))
item.refId = MWWorld::ContainerStore::sGoldId;
else
item.refId = itemPtr.getCellRef().getRefId();
item.count = count;
item.charge = itemPtr.getCellRef().getCharge();
item.enchantmentCharge = itemPtr.getCellRef().getEnchantmentCharge();
item.soul = itemPtr.getCellRef().getSoul();
return item;
}
mwmp::Target MechanicsHelper::getTarget(const MWWorld::Ptr& ptr)
{
mwmp::Target target;
@ -393,18 +410,14 @@ void MechanicsHelper::processAttack(Attack attack, const MWWorld::Ptr& attacker)
break;
}
if (it != inventoryStore.end())
{
inventoryStore.setSelectedEnchantItem(it);
LOG_APPEND(Log::LOG_VERBOSE, "- itemId: %s", attack.itemId.c_str());
MWBase::Environment::get().getWorld()->castSpell(attacker);
inventoryStore.setSelectedEnchantItem(inventoryStore.end());
}
else
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Could not find item %s used by %s to cast item spell!",
attack.itemId.c_str(), attacker.getCellRef().getRefId().c_str());
}
// Add the item if it's missing
if (it == inventoryStore.end())
it = attacker.getClass().getContainerStore(attacker).add(attack.itemId, 1, attacker);
inventoryStore.setSelectedEnchantItem(it);
LOG_APPEND(Log::LOG_VERBOSE, "- itemId: %s", attack.itemId.c_str());
MWBase::Environment::get().getWorld()->castSpell(attacker);
inventoryStore.setSelectedEnchantItem(inventoryStore.end());
}
}
@ -452,17 +465,25 @@ void MechanicsHelper::unequipItemsByEffect(const MWWorld::Ptr& ptr, short enchan
MWWorld::Ptr MechanicsHelper::getItemPtrFromStore(const mwmp::Item& item, MWWorld::ContainerStore& store)
{
MWWorld::Ptr closestPtr;
for (MWWorld::ContainerStoreIterator storeIterator = store.begin(); storeIterator != store.end(); ++storeIterator)
{
// Enchantment charges are often in the process of refilling themselves, so don't check for them here
if (Misc::StringUtils::ciEqual(item.refId, storeIterator->getCellRef().getRefId()) &&
item.count == storeIterator->getRefData().getCount() &&
item.charge == storeIterator->getCellRef().getCharge() &&
item.enchantmentCharge == storeIterator->getCellRef().getEnchantmentCharge() &&
Misc::StringUtils::ciEqual(item.soul, storeIterator->getCellRef().getSoul()))
{
return *storeIterator;
// If we have no closestPtr, set it to the Ptr corresponding to this storeIterator; otherwise, make
// sure the storeIterator's enchantmentCharge is closer to our goal than that of the previous closestPtr
if (!closestPtr || abs(storeIterator->getCellRef().getEnchantmentCharge() - item.enchantmentCharge) <
abs(closestPtr.getCellRef().getEnchantmentCharge() - item.enchantmentCharge))
{
closestPtr = *storeIterator;
}
}
}
return 0;
return closestPtr;
}

View file

@ -22,6 +22,7 @@ namespace MechanicsHelper
MWWorld::Ptr getPlayerPtr(const mwmp::Target& target);
mwmp::Item getItem(const MWWorld::Ptr& itemPtr, int count);
mwmp::Target getTarget(const MWWorld::Ptr& ptr);
void clearTarget(mwmp::Target& target);
bool isEmptyTarget(const mwmp::Target& target);

View file

@ -45,7 +45,7 @@ using namespace mwmp;
string listDiscrepancies(PacketPreInit::PluginContainer checksums, PacketPreInit::PluginContainer checksumsResponse)
{
std::ostringstream sstr;
sstr << "Your plugins or their load order don't match the server's. A full comparison is included in your client console and latest log file. In short, the following discrepancies have been found:\n\n";
sstr << "Your plugins or their load order don't match the server's. A full comparison is included in your debug window and latest log file. In short, the following discrepancies have been found:\n\n";
int discrepancyCount = 0;

View file

@ -17,6 +17,8 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwgui/container.hpp"
#include "../mwgui/inventorywindow.hpp"
#include "../mwgui/windowmanagerimp.hpp"
#include "../mwmechanics/aifollow.hpp"
#include "../mwmechanics/spellcasting.hpp"
@ -321,7 +323,17 @@ void ObjectList::activateObjects(MWWorld::CellStore* cellStore)
if (activatingActorPtr)
{
MWBase::Environment::get().getWorld()->activate(ptrFound, activatingActorPtr);
// Is an item that can be picked up being activated by the local player with their inventory open?
if (activatingActorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr() &&
(MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Container ||
MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Inventory))
{
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(ptrFound);
}
else
{
MWBase::Environment::get().getWorld()->activate(ptrFound, activatingActorPtr);
}
}
}
}
@ -333,7 +345,7 @@ void ObjectList::placeObjects(MWWorld::CellStore* cellStore)
for (const auto &baseObject : baseObjects)
{
LOG_APPEND(Log::LOG_VERBOSE, "- cellRef: %s %i-%i, count: %i, charge: %i, enchantmentCharge: %i, soul: %s",
LOG_APPEND(Log::LOG_VERBOSE, "- cellRef: %s %i-%i, count: %i, charge: %i, enchantmentCharge: %.2f, soul: %s",
baseObject.refId.c_str(), baseObject.refNum, baseObject.mpNum, baseObject.count, baseObject.charge,
baseObject.enchantmentCharge, baseObject.soul.c_str());

View file

@ -92,6 +92,17 @@ bool PlayerList::isDedicatedPlayer(const MWWorld::Ptr &ptr)
return (getPlayer(ptr) != 0);
}
void PlayerList::enableMarkers(const ESM::Cell& cell)
{
for (auto &playerEntry : players)
{
if (Main::get().getCellController()->isSameCell(cell, playerEntry.second->cell))
{
playerEntry.second->enableMarker();
}
}
}
/*
Go through all DedicatedPlayers checking if their mHitAttemptActorId matches this one
and set it to -1 if it does

View file

@ -37,6 +37,8 @@ namespace mwmp
static bool isDedicatedPlayer(const MWWorld::Ptr &ptr);
static void enableMarkers(const ESM::Cell& cell);
static void clearHitAttemptActorId(int actorId);
private:

View file

@ -110,7 +110,7 @@ void RecordHelper::overrideCreatureRecord(const mwmp::CreatureRecord& record)
if (recordData.mId.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
return;
}
@ -164,6 +164,11 @@ void RecordHelper::overrideCreatureRecord(const mwmp::CreatureRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -175,7 +180,7 @@ void RecordHelper::overrideNpcRecord(const mwmp::NpcRecord& record)
if (recordData.mId.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
return;
}
@ -186,12 +191,12 @@ void RecordHelper::overrideNpcRecord(const mwmp::NpcRecord& record)
{
if (!doesRaceRecordExist(recordData.mRace))
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new NPC record with invalid race provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new NPC record with invalid race provided");
return;
}
else if (!doesClassRecordExist(recordData.mClass))
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new NPC record with invalid class provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new NPC record with invalid class provided");
return;
}
else
@ -272,6 +277,11 @@ void RecordHelper::overrideNpcRecord(const mwmp::NpcRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -283,18 +293,17 @@ void RecordHelper::overrideEnchantmentRecord(const mwmp::EnchantmentRecord& reco
if (recordData.mId.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
return;
}
bool isExistingId = doesEnchantmentRecordExist(recordData.mId);
MWBase::World *world = MWBase::Environment::get().getWorld();
if (record.baseId.empty())
{
if (recordData.mEffects.mList.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new enchantment record with no effects");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new enchantment record with no effects");
return;
}
else
@ -323,6 +332,11 @@ void RecordHelper::overrideEnchantmentRecord(const mwmp::EnchantmentRecord& reco
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
}
void RecordHelper::overridePotionRecord(const mwmp::PotionRecord& record)
@ -331,7 +345,7 @@ void RecordHelper::overridePotionRecord(const mwmp::PotionRecord& record)
if (recordData.mId.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
return;
}
@ -374,6 +388,11 @@ void RecordHelper::overridePotionRecord(const mwmp::PotionRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -385,7 +404,7 @@ void RecordHelper::overrideSpellRecord(const mwmp::SpellRecord& record)
if (recordData.mId.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
return;
}
@ -419,9 +438,11 @@ void RecordHelper::overrideSpellRecord(const mwmp::SpellRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
}
void RecordHelper::overrideArmorRecord(const mwmp::ArmorRecord& record)
@ -430,7 +451,7 @@ void RecordHelper::overrideArmorRecord(const mwmp::ArmorRecord& record)
if (recordData.mId.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
return;
}
@ -441,7 +462,7 @@ void RecordHelper::overrideArmorRecord(const mwmp::ArmorRecord& record)
{
if (!recordData.mEnchant.empty() && !doesEnchantmentRecordExist(recordData.mEnchant))
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new armor record with invalid enchantment provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new armor record with invalid enchantmentId %s", recordData.mEnchant.c_str());
return;
}
else
@ -479,8 +500,13 @@ void RecordHelper::overrideArmorRecord(const mwmp::ArmorRecord& record)
if (record.baseOverrides.hasArmorRating)
finalData.mData.mArmor = recordData.mData.mArmor;
if (record.baseOverrides.hasEnchantmentId && doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
if (record.baseOverrides.hasEnchantmentId)
{
if (doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
else
LOG_APPEND(Log::LOG_INFO, "-- Ignoring invalid enchantmentId %s", recordData.mEnchant.c_str());
}
if (record.baseOverrides.hasEnchantmentCharge)
finalData.mData.mEnchant = recordData.mData.mEnchant;
@ -493,6 +519,11 @@ void RecordHelper::overrideArmorRecord(const mwmp::ArmorRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -504,7 +535,7 @@ void RecordHelper::overrideBookRecord(const mwmp::BookRecord& record)
if (recordData.mId.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
return;
}
@ -515,7 +546,7 @@ void RecordHelper::overrideBookRecord(const mwmp::BookRecord& record)
{
if (!recordData.mEnchant.empty() && !doesEnchantmentRecordExist(recordData.mEnchant))
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new book record with invalid enchantment provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new book record with invalid enchantmentId %s", recordData.mEnchant.c_str());
return;
}
else
@ -551,8 +582,13 @@ void RecordHelper::overrideBookRecord(const mwmp::BookRecord& record)
if (record.baseOverrides.hasSkillId)
finalData.mData.mSkillId = recordData.mData.mSkillId;
if (record.baseOverrides.hasEnchantmentId && doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
if (record.baseOverrides.hasEnchantmentId)
{
if (doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
else
LOG_APPEND(Log::LOG_INFO, "-- Ignoring invalid enchantmentId %s", recordData.mEnchant.c_str());
}
if (record.baseOverrides.hasEnchantmentCharge)
finalData.mData.mEnchant = recordData.mData.mEnchant;
@ -562,6 +598,11 @@ void RecordHelper::overrideBookRecord(const mwmp::BookRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -573,7 +614,7 @@ void RecordHelper::overrideClothingRecord(const mwmp::ClothingRecord& record)
if (recordData.mId.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
return;
}
@ -584,7 +625,7 @@ void RecordHelper::overrideClothingRecord(const mwmp::ClothingRecord& record)
{
if (!recordData.mEnchant.empty() && !doesEnchantmentRecordExist(recordData.mEnchant))
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new clothing record with invalid enchantment provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new clothing record with invalid enchantmentId %s", recordData.mEnchant.c_str());
return;
}
else
@ -614,8 +655,13 @@ void RecordHelper::overrideClothingRecord(const mwmp::ClothingRecord& record)
if (record.baseOverrides.hasValue)
finalData.mData.mValue = recordData.mData.mValue;
if (record.baseOverrides.hasEnchantmentId && doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
if (record.baseOverrides.hasEnchantmentId)
{
if (doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
else
LOG_APPEND(Log::LOG_INFO, "-- Ignoring invalid enchantmentId %s", recordData.mEnchant.c_str());
}
if (record.baseOverrides.hasEnchantmentCharge)
finalData.mData.mEnchant = recordData.mData.mEnchant;
@ -628,6 +674,11 @@ void RecordHelper::overrideClothingRecord(const mwmp::ClothingRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -639,7 +690,7 @@ void RecordHelper::overrideMiscellaneousRecord(const mwmp::MiscellaneousRecord&
if (recordData.mId.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
return;
}
@ -679,6 +730,11 @@ void RecordHelper::overrideMiscellaneousRecord(const mwmp::MiscellaneousRecord&
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);
@ -690,7 +746,7 @@ void RecordHelper::overrideWeaponRecord(const mwmp::WeaponRecord& record)
if (recordData.mId.empty())
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring record override with no id provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with no id provided");
return;
}
@ -701,7 +757,7 @@ void RecordHelper::overrideWeaponRecord(const mwmp::WeaponRecord& record)
{
if (!recordData.mEnchant.empty() && !doesEnchantmentRecordExist(recordData.mEnchant))
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Ignoring new weapon record with invalid enchantment provided");
LOG_APPEND(Log::LOG_INFO, "-- Ignoring new weapon record with invalid enchantmentId %s", recordData.mEnchant.c_str());
return;
}
else
@ -761,8 +817,13 @@ void RecordHelper::overrideWeaponRecord(const mwmp::WeaponRecord& record)
if (record.baseOverrides.hasFlags)
finalData.mData.mFlags = recordData.mData.mFlags;
if (record.baseOverrides.hasEnchantmentId && doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
if (record.baseOverrides.hasEnchantmentId)
{
if (doesEnchantmentRecordExist(recordData.mEnchant))
finalData.mEnchant = recordData.mEnchant;
else
LOG_APPEND(Log::LOG_INFO, "-- Ignoring invalid enchantmentId %s", recordData.mEnchant.c_str());
}
if (record.baseOverrides.hasEnchantmentCharge)
finalData.mData.mEnchant = recordData.mData.mEnchant;
@ -772,6 +833,11 @@ void RecordHelper::overrideWeaponRecord(const mwmp::WeaponRecord& record)
world->getModifiableStore().overrideRecord(finalData);
}
else
{
LOG_APPEND(Log::LOG_INFO, "-- Ignoring record override with invalid baseId %s", record.baseId.c_str());
return;
}
if (isExistingId)
world->updatePtrsWithRefId(recordData.mId);

View file

@ -35,7 +35,7 @@ Networking *Worldstate::getNetworking()
void Worldstate::addRecords()
{
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Received ID_RECORD_DYNAMIC with %i records of type %i",
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Received ID_RECORD_DYNAMIC with %i records of type %i",
recordsCount, recordsType);
if (recordsType == mwmp::RECORD_TYPE::SPELL)
@ -44,7 +44,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_ERROR, "- spell record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_INFO, "- spell record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideSpellRecord(record);
@ -56,7 +56,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_ERROR, "- potion record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_INFO, "- potion record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overridePotionRecord(record);
@ -68,7 +68,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_ERROR, "- enchantment record %s, %i\n-- baseId is %s", record.data.mId.c_str(), record.data.mData.mType,
LOG_APPEND(Log::LOG_INFO, "- enchantment record %s, %i\n-- baseId is %s", record.data.mId.c_str(), record.data.mData.mType,
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideEnchantmentRecord(record);
@ -80,7 +80,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_ERROR, "- creature record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_INFO, "- creature record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideCreatureRecord(record);
@ -92,7 +92,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_ERROR, "- NPC record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_INFO, "- NPC record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideNpcRecord(record);
@ -104,7 +104,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_ERROR, "- armor record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_INFO, "- armor record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideArmorRecord(record);
@ -116,7 +116,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_ERROR, "- book record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_INFO, "- book record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideBookRecord(record);
@ -128,7 +128,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_ERROR, "- clothing record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_INFO, "- clothing record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideClothingRecord(record);
@ -140,7 +140,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_ERROR, "- miscellaneous record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_INFO, "- miscellaneous record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideMiscellaneousRecord(record);
@ -152,7 +152,7 @@ void Worldstate::addRecords()
{
bool hasBaseId = !record.baseId.empty();
LOG_APPEND(Log::LOG_ERROR, "- weapon record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
LOG_APPEND(Log::LOG_INFO, "- weapon record %s, %s\n-- baseId is %s", record.data.mId.c_str(), record.data.mName.c_str(),
hasBaseId ? record.baseId.c_str() : "empty");
RecordHelper::overrideWeaponRecord(record);
@ -251,7 +251,7 @@ void Worldstate::sendEnchantmentRecord(const ESM::Enchantment* enchantment)
{
enchantmentRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with enchantment");
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with enchantment");
recordsType = mwmp::RECORD_TYPE::ENCHANTMENT;
@ -267,7 +267,7 @@ void Worldstate::sendPotionRecord(const ESM::Potion* potion)
{
potionRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with potion %s", potion->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with potion %s", potion->mName.c_str());
recordsType = mwmp::RECORD_TYPE::POTION;
@ -283,7 +283,7 @@ void Worldstate::sendSpellRecord(const ESM::Spell* spell)
{
spellRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with spell %s", spell->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with spell %s", spell->mName.c_str());
recordsType = mwmp::RECORD_TYPE::SPELL;
@ -299,7 +299,7 @@ void Worldstate::sendArmorRecord(const ESM::Armor* armor, std::string baseId)
{
armorRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with armor %s", armor->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with armor %s", armor->mName.c_str());
recordsType = mwmp::RECORD_TYPE::ARMOR;
@ -319,7 +319,7 @@ void Worldstate::sendBookRecord(const ESM::Book* book, std::string baseId)
{
bookRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with book %s", book->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with book %s", book->mName.c_str());
recordsType = mwmp::RECORD_TYPE::BOOK;
@ -339,7 +339,7 @@ void Worldstate::sendClothingRecord(const ESM::Clothing* clothing, std::string b
{
clothingRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with clothing %s", clothing->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with clothing %s", clothing->mName.c_str());
recordsType = mwmp::RECORD_TYPE::CLOTHING;
@ -359,7 +359,7 @@ void Worldstate::sendWeaponRecord(const ESM::Weapon* weapon, std::string baseId)
{
weaponRecords.clear();
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Sending ID_RECORD_DYNAMIC with weapon %s", weapon->mName.c_str());
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_RECORD_DYNAMIC with weapon %s", weapon->mName.c_str());
recordsType = mwmp::RECORD_TYPE::WEAPON;

View file

@ -89,7 +89,7 @@
#include "WorldstateProcessor.hpp"
#include "worldstate/ProcessorCellCreate.hpp"
#include "worldstate/ProcessorCellReplace.hpp"
#include "worldstate/ProcessorCellReset.hpp"
#include "worldstate/ProcessorRecordDynamic.hpp"
#include "worldstate/ProcessorWorldCollisionOverride.hpp"
#include "worldstate/ProcessorWorldMap.hpp"
@ -186,7 +186,7 @@ void ProcessorInitializer()
ActorProcessor::AddProcessor(new ProcessorActorTest());
WorldstateProcessor::AddProcessor(new ProcessorCellCreate());
WorldstateProcessor::AddProcessor(new ProcessorCellReplace());
WorldstateProcessor::AddProcessor(new ProcessorCellReset());
WorldstateProcessor::AddProcessor(new ProcessorRecordDynamic());
WorldstateProcessor::AddProcessor(new ProcessorWorldCollisionOverride());
WorldstateProcessor::AddProcessor(new ProcessorWorldMap());

View file

@ -28,7 +28,7 @@ namespace mwmp
if (!isRequest())
{
LOG_APPEND(Log::LOG_INFO, "- refId: %s, count: %i, charge: %f, enchantmentCharge: %f, soul: %s",
LOG_APPEND(Log::LOG_INFO, "- refId: %s, count: %i, charge: %i, enchantmentCharge: %f, soul: %s",
player->usedItem.refId.c_str(), player->usedItem.count, player->usedItem.charge,
player->usedItem.enchantmentCharge, player->usedItem.soul.c_str());
@ -36,7 +36,11 @@ namespace mwmp
MWWorld::InventoryStore &inventoryStore = playerPtr.getClass().getInventoryStore(playerPtr);
MWWorld::Ptr itemPtr = MechanicsHelper::getItemPtrFromStore(player->usedItem, inventoryStore);
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(itemPtr);
if (itemPtr)
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(itemPtr);
else
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Cannot use non-existent item %s", player->usedItem.refId.c_str());
}
}
};

View file

@ -1,23 +0,0 @@
#ifndef OPENMW_PROCESSORCELLREPLACE_HPP
#define OPENMW_PROCESSORCELLREPLACE_HPP
#include "../WorldstateProcessor.hpp"
namespace mwmp
{
class ProcessorCellReplace : public WorldstateProcessor
{
public:
ProcessorCellReplace()
{
BPP_INIT(ID_CELL_REPLACE)
}
virtual void Do(WorldstatePacket &packet, Worldstate &worldstate)
{
// Placeholder
}
};
}
#endif //OPENMW_PROCESSORCELLREPLACE_HPP

View file

@ -0,0 +1,23 @@
#ifndef OPENMW_PROCESSORCELLRESET_HPP
#define OPENMW_PROCESSORCELLRESET_HPP
#include "../WorldstateProcessor.hpp"
namespace mwmp
{
class ProcessorCellReset : public WorldstateProcessor
{
public:
ProcessorCellReset()
{
BPP_INIT(ID_CELL_RESET)
}
virtual void Do(WorldstatePacket &packet, Worldstate &worldstate)
{
// Placeholder
}
};
}
#endif //OPENMW_PROCESSORCELLRESET_HPP

View file

@ -80,18 +80,24 @@ namespace MWScript
/*
Start of tes3mp change (major)
Disable unilateral item addition on this client and expect the server's reply to our
packet to do it instead, except for changes to player inventories which still require
the PlayerInventory to be reworked
Allow unilateral item removal on this client from client scripts and dialogue (but not console commands)
to prevent infinite loops in certain mods. Otherwise, expect the server's reply to our packet to do the
removal instead, except for changes to player inventories which still require the PlayerInventory to be
reworked.
*/
// Spawn a messagebox (only for items added to player's inventory and if player is talking to someone)
if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr() )
{
MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore(ptr).add(item, count, ptr);
unsigned char packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
MWWorld::Ptr itemPtr;
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() || packetOrigin != mwmp::CLIENT_CONSOLE)
itemPtr = *ptr.getClass().getContainerStore(ptr).add(item, count, ptr);
/*
End of tes3mp change (major)
*/
// Spawn a messagebox (only for items added to player's inventory and if player is talking to someone)
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())
{
// The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory
std::string msgBox;
std::string itemName = itemPtr.getClass().getName(itemPtr);
@ -113,11 +119,12 @@ namespace MWScript
Send an ID_CONTAINER packet every time an item is added to a Ptr
that doesn't belong to a DedicatedPlayer
*/
else if (!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr))
else if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() &&
(!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr)))
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->packetOrigin = packetOrigin;
objectList->cell = *ptr.getCell()->getCell();
objectList->action = mwmp::BaseObjectList::ADD;
objectList->containerSubAction = mwmp::BaseObjectList::NONE;
@ -202,13 +209,15 @@ namespace MWScript
/*
Start of tes3mp change (major)
Disable unilateral item removal on this client and expect the server's reply to our
packet to do it instead, except for changes to player inventories which still require
the PlayerInventory to be reworked
Allow unilateral item removal on this client from client scripts and dialogue (but not console commands)
to prevent infinite loops in certain mods. Otherwise, expect the server's reply to our packet to do the
removal instead, except for changes to player inventories which still require the PlayerInventory to be
reworked.
*/
unsigned char packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
int numRemoved = 0;
if (ptr == MWMechanics::getPlayer())
if (ptr == MWMechanics::getPlayer() || packetOrigin != mwmp::CLIENT_CONSOLE)
numRemoved = store.remove(item, count, ptr);
// Spawn a messagebox (only for items removed from player's inventory)
@ -240,11 +249,12 @@ namespace MWScript
Send an ID_CONTAINER packet every time an item is removed from a Ptr
that doesn't belong to a DedicatedPlayer
*/
else if (!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr))
else if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() &&
(!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr)))
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->packetOrigin = packetOrigin;
objectList->cell = *ptr.getCell()->getCell();
objectList->action = mwmp::BaseObjectList::REMOVE;
objectList->containerSubAction = mwmp::BaseObjectList::NONE;

View file

@ -63,7 +63,7 @@ namespace MWScript
Send an ID_PLAYER_JOURNAL packet every time a new journal entry is added
through a script
*/
if (!MWBase::Environment::get().getJournal()->hasEntry(quest, index))
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() && !MWBase::Environment::get().getJournal()->hasEntry(quest, index))
mwmp::Main::get().getLocalPlayer()->sendJournalEntry(quest, index, ptr);
/*
End of tes3mp addition
@ -99,7 +99,8 @@ namespace MWScript
Send an ID_PLAYER_JOURNAL packet every time a journal index is set
through a script
*/
mwmp::Main::get().getLocalPlayer()->sendJournalIndex(quest, index);
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
mwmp::Main::get().getLocalPlayer()->sendJournalIndex(quest, index);
/*
End of tes3mp addition
*/
@ -137,7 +138,8 @@ namespace MWScript
Send an ID_PLAYER_TOPIC packet every time a new topic is added
through a script
*/
if (MWBase::Environment::get().getDialogueManager()->isNewTopic(Misc::StringUtils::lowerCase(topic)))
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() &&
MWBase::Environment::get().getDialogueManager()->isNewTopic(Misc::StringUtils::lowerCase(topic)))
mwmp::Main::get().getLocalPlayer()->sendTopic(Misc::StringUtils::lowerCase(topic));
/*
End of tes3mp addition

View file

@ -613,13 +613,17 @@ namespace MWScript
Start of tes3mp addition
Send an ID_OBJECT_STATE packet whenever an object is enabled, as long as
the player has finished character generation and the object wasn't already
enabled previously
the player is logged in on the server, the object is still disabled, and our last
packet regarding its state did not already attempt to enable it (to prevent
packet spam)
*/
if (mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen())
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
if (ref.isInCell() && !ref.getRefData().isEnabled())
if (ref.isInCell() && !ref.getRefData().isEnabled() &&
ref.getRefData().getLastCommunicatedState() != MWWorld::RefData::StateCommunication::Enabled)
{
ref.getRefData().setLastCommunicatedState(MWWorld::RefData::StateCommunication::Enabled);
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(getContextType());
@ -650,14 +654,18 @@ namespace MWScript
/*
Start of tes3mp addition
Send an ID_OBJECT_STATE packet whenever an object is disabled, as long as
the player has finished character generation and the object wasn't already
disabled previously
Send an ID_OBJECT_STATE packet whenever an object should be disabled, as long as
the player is logged in on the server, the object is still enabled, and our last
packet regarding its state did not already attempt to disable it (to prevent
packet spam)
*/
if (mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen())
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
if (ref.isInCell() && ref.getRefData().isEnabled())
if (ref.isInCell() && ref.getRefData().isEnabled() &&
ref.getRefData().getLastCommunicatedState() != MWWorld::RefData::StateCommunication::Disabled)
{
ref.getRefData().setLastCommunicatedState(MWWorld::RefData::StateCommunication::Disabled);
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(getContextType());

View file

@ -9,6 +9,7 @@
*/
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp"
#include "../mwmp/ObjectList.hpp"
#include "../mwmp/ScriptController.hpp"
/*
@ -105,11 +106,14 @@ namespace MWScript
Send an ID_VIDEO_PLAY packet every time a video is played
through a script
*/
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addVideoPlay(name, allowSkipping);
objectList->sendVideoPlay();
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addVideoPlay(name, allowSkipping);
objectList->sendVideoPlay();
}
/*
End of tes3mp addition
*/
@ -216,11 +220,14 @@ namespace MWScript
Send an ID_OBJECT_LOCK packet every time an object is locked
through a script
*/
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectLock(ptr, lockLevel);
objectList->sendObjectLock();
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectLock(ptr, lockLevel);
objectList->sendObjectLock();
}
/*
End of tes3mp addition
*/
@ -266,11 +273,14 @@ namespace MWScript
Send an ID_OBJECT_LOCK packet every time an object is unlocked
through a script
*/
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectLock(ptr, 0);
objectList->sendObjectLock();
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectLock(ptr, 0);
objectList->sendObjectLock();
}
/*
End of tes3mp addition
*/
@ -768,13 +778,20 @@ namespace MWScript
Start of tes3mp addition
Send an ID_OBJECT_DELETE packet every time an object is deleted
through a script
through a script, as long as we haven't already communicated
a deletion for it
*/
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectDelete(ptr);
objectList->sendObjectDelete();
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() &&
ptr.getRefData().getLastCommunicatedState() != MWWorld::RefData::StateCommunication::Deleted)
{
ptr.getRefData().setLastCommunicatedState(MWWorld::RefData::StateCommunication::Deleted);
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
objectList->addObjectDelete(ptr);
objectList->sendObjectDelete();
}
/*
End of tes3mp addition
*/

View file

@ -470,18 +470,24 @@ namespace MWScript
// make sure a spell with this ID actually exists.
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (id);
ptr.getClass().getCreatureStats (ptr).getSpells().add (id);
/*
Start of tes3mp addition
Start of tes3mp change (major)
Send an ID_PLAYER_SPELLBOOK packet every time a player gains a spell
through a script
Only add the spell if the target doesn't already have it
Send an ID_PLAYER_SPELLBOOK packet every time a player gains a spell here
*/
if (ptr == MWMechanics::getPlayer())
mwmp::Main::get().getLocalPlayer()->sendSpellChange(id, mwmp::SpellbookChanges::ADD);
MWMechanics::Spells &spells = ptr.getClass().getCreatureStats(ptr).getSpells();
if (!spells.hasSpell(id))
{
spells.add(id);
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() && ptr == MWMechanics::getPlayer())
mwmp::Main::get().getLocalPlayer()->sendSpellChange(id, mwmp::SpellbookChanges::ADD);
}
/*
End of tes3mp addition
End of tes3mp change (major)
*/
}
};
@ -498,26 +504,32 @@ namespace MWScript
std::string id = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
ptr.getClass().getCreatureStats (ptr).getSpells().remove (id);
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
if (ptr == MWMechanics::getPlayer() &&
id == wm->getSelectedSpell())
{
wm->unsetSelectedSpell();
}
/*
Start of tes3mp addition
Start of tes3mp change (major)
Send an ID_PLAYER_SPELLBOOK packet every time a player loses a spell
through a script
Only remove the spell if the target has it
Send an ID_PLAYER_SPELLBOOK packet every time a player loses a spell here
*/
if (ptr == MWMechanics::getPlayer())
mwmp::Main::get().getLocalPlayer()->sendSpellChange(id, mwmp::SpellbookChanges::REMOVE);
MWMechanics::Spells &spells = ptr.getClass().getCreatureStats(ptr).getSpells();
if (spells.hasSpell(id))
{
ptr.getClass().getCreatureStats(ptr).getSpells().remove(id);
if (ptr == MWMechanics::getPlayer())
{
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
if (id == wm->getSelectedSpell())
wm->unsetSelectedSpell();
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
mwmp::Main::get().getLocalPlayer()->sendSpellChange(id, mwmp::SpellbookChanges::REMOVE);
}
}
/*
End of tes3mp addition
End of tes3mp change (major)
*/
}
};

View file

@ -63,15 +63,14 @@ namespace MWScript
Prevent players from changing their own scale
Send an ID_OBJECT_SCALE every time an object's
scale is changed through a script
Send an ID_OBJECT_SCALE every time an object's scale is changed through a script
*/
if (ptr == MWMechanics::getPlayer())
{
MWBase::Environment::get().getWindowManager()->
messageBox("You can't change your own scale in multiplayer. Only the server can.");
}
else if (ptr.isInCell() && ptr.getCellRef().getScale() != scale)
else if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() && ptr.isInCell() && ptr.getCellRef().getScale() != scale)
{
// Ignore attempts to change another player's scale
if (mwmp::PlayerList::isDedicatedPlayer(ptr))
@ -504,6 +503,44 @@ namespace MWScript
ref.getPtr().getCellRef().setPosition(pos);
MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos);
placed.getClass().adjustPosition(placed, true);
/*
Start of tes3mp addition
Send an ID_OBJECT_PLACE or ID_OBJECT_SPAWN packet every time an object is placed
in the world through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
if (placed.getClass().isActor())
{
objectList->addObjectSpawn(placed);
objectList->sendObjectSpawn();
}
else
{
objectList->addObjectPlace(placed);
objectList->sendObjectPlace();
}
}
/*
End of tes3mp addition
*/
/*
Start of tes3mp change (major)
Instead of actually keeping this object as is, delete it after sending the packet
and wait for the server to send it back with a unique mpNum of its own
*/
MWBase::Environment::get().getWorld()->deleteObject(placed);
/*
End of tes3mp change (major)
*/
}
}
};
@ -552,6 +589,44 @@ namespace MWScript
ref.getPtr().getCellRef().setPosition(pos);
MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos);
placed.getClass().adjustPosition(placed, true);
/*
Start of tes3mp addition
Send an ID_OBJECT_PLACE or ID_OBJECT_SPAWN packet every time an object is placed
in the world through a script
*/
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
if (placed.getClass().isActor())
{
objectList->addObjectSpawn(placed);
objectList->sendObjectSpawn();
}
else
{
objectList->addObjectPlace(placed);
objectList->sendObjectPlace();
}
}
/*
End of tes3mp addition
*/
/*
Start of tes3mp change (major)
Instead of actually keeping this object as is, delete it after sending the packet
and wait for the server to send it back with a unique mpNum of its own
*/
MWBase::Environment::get().getWorld()->deleteObject(placed);
/*
End of tes3mp change (major)
*/
}
};
@ -599,19 +674,22 @@ namespace MWScript
Send an ID_OBJECT_PLACE or ID_OBJECT_SPAWN packet every time an object is placed
in the world through a script
*/
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
if (ptr.getClass().isActor())
{
objectList->addObjectSpawn(ptr);
objectList->sendObjectSpawn();
}
else
{
objectList->addObjectPlace(ptr);
objectList->sendObjectPlace();
if (ptr.getClass().isActor())
{
objectList->addObjectSpawn(ptr);
objectList->sendObjectSpawn();
}
else
{
objectList->addObjectPlace(ptr);
objectList->sendObjectPlace();
}
}
/*
End of tes3mp addition

View file

@ -194,6 +194,25 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr,
if (ptr.getRefData().getCount() <= count)
return end();
MWWorld::ContainerStoreIterator it = addNewStack(ptr, ptr.getRefData().getCount()-count);
/*
Start of tes3mp addition
Send an ID_PLAYER_INVENTORY packet every time an item stack gets added for a player here
*/
Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (container == player && this == &player.getClass().getContainerStore(player))
{
mwmp::LocalPlayer *localPlayer = mwmp::Main::get().getLocalPlayer();
if (!localPlayer->isReceivingInventory)
localPlayer->sendItemChange(ptr, ptr.getRefData().getCount() - count, mwmp::InventoryChanges::ADD);
}
/*
End of tes3mp addition
*/
const std::string script = it->getClass().getScript(*it);
if (!script.empty())
MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it);
@ -366,8 +385,18 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1);
}
if (mListener)
mListener->itemAdded(item, count);
/*
Start of tes3mp change (major)
Disable the listener here because it keeps causing crashes; this should only be
a temporary solution that doesn't affect much anyway given that the listener is
only used in relation to light-emitting items
*/
//if (mListener)
// mListener->itemAdded(item, count);
/*
End of tes3mp change (major)
*/
return it;
}
@ -493,8 +522,18 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
flagAsModified();
if (mListener)
mListener->itemRemoved(item, count - toRemove);
/*
Start of tes3mp change (major)
Disable the listener here because it keeps causing crashes; this should only be
a temporary solution that doesn't affect much anyway given that the listener is
only used in relation to light-emitting items
*/
//if (mListener)
// mListener->itemRemoved(item, count - toRemove);
/*
End of tes3mp change (major)
*/
// number of removed items
return count - toRemove;

View file

@ -13,6 +13,7 @@
Include additional headers for multiplayer purposes
*/
#include <components/openmw-mp/Log.hpp>
#include "../mwmp/Main.hpp"
#include "../mwmp/CellController.hpp"
#include "../mwmp/PlayerList.hpp"
@ -251,7 +252,19 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot (int slot) con
{
// Object has been deleted
// This should no longer happen, since the new remove function will unequip first
throw std::runtime_error("Invalid slot, make sure you are not calling RefData::setCount for a container object");
/*
Start of tes3mp change (major)
Instead of throwing an error, display an error log message with information about
the item
*/
//throw std::runtime_error("Invalid slot, make sure you are not calling RefData::setCount for a container object");
LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Invalid slot, make sure you are not calling RefData::setCount for a container object\n- item was %s",
mSlots[slot]->getCellRef().getRefId().c_str());
/*
End of tes3mp change (major)
*/
}
return mSlots[slot];

View file

@ -138,6 +138,35 @@ namespace MWWorld
const ESM::AnimationState& getAnimationState() const;
ESM::AnimationState& getAnimationState();
/*
Start of tes3mp addition
Track the last state communicated to the server for this reference,
to avoid packet spam when the server denies our state change request or
is slow to reply
*/
enum StateCommunication
{
None = 0,
Enabled = 1,
Disabled = 2,
Deleted = 3
};
private:
short mLastCommunicatedState = StateCommunication::None;
public:
short getLastCommunicatedState() { return mLastCommunicatedState; };
void setLastCommunicatedState(short communicationState) { mLastCommunicatedState = communicationState; };
/*
End of tes3mp addition
*/
};
}

View file

@ -489,9 +489,9 @@ namespace MWWorld
Start of tes3mp addition
Send an ID_PLAYER_CELL_STATE packet with all cell states stored in LocalPlayer
and then clear them, but only if the player has finished character generation
and then clear them, but only if the player is logged in on the server
*/
if (mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen())
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::Main::get().getLocalPlayer()->sendCellStates();
mwmp::Main::get().getLocalPlayer()->clearCellStates();
@ -631,9 +631,9 @@ namespace MWWorld
Start of tes3mp addition
Send an ID_PLAYER_CELL_STATE packet with all cell states stored in LocalPlayer
and then clear them, but only if the player has finished character generation
and then clear them, but only if the player is logged in on the server
*/
if (mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen())
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
{
mwmp::Main::get().getLocalPlayer()->sendCellStates();
mwmp::Main::get().getLocalPlayer()->clearCellStates();

View file

@ -282,24 +282,16 @@ namespace MWWorld
/*
Start of tes3mp change (major)
If Pelagiad exists, spawn there; otherwise, spawn at 0 ,0
Spawn at 0, -7 by default
*/
if (findExteriorPosition("Pelagiad", pos))
{
changeToExteriorCell(pos, true);
fixPosition(getPlayerPtr());
}
else
{
const int cellSize = 8192;
pos.pos[0] = cellSize / 2;
pos.pos[1] = cellSize / 2;
pos.pos[2] = 0;
pos.rot[0] = 0;
pos.rot[1] = 0;
pos.rot[2] = 0;
mWorldScene->changeToExteriorCell(pos, true);
}
const int cellSize = 8192;
pos.pos[0] = cellSize / 2;
pos.pos[1] = cellSize * -7 + cellSize / 2;
pos.pos[2] = 0;
pos.rot[0] = 0;
pos.rot[1] = 0;
pos.rot[2] = 0;
mWorldScene->changeToExteriorCell(pos, true);
/*
End of tes3mp change (major)
*/
@ -3130,6 +3122,18 @@ namespace MWWorld
End of tes3mp addition
*/
/*
Start of tes3mp addition
Always start spells cast by DedicatedPlayers and DedicatedActors,
without unilaterally deducting any magicka for them on this client
*/
if (mwmp::PlayerList::isDedicatedPlayer(actor) || mwmp::Main::get().getCellController()->isDedicatedActor(actor))
return true;
/*
End of tes3mp addition
*/
const ESM::Spell* spell = getStore().get<ESM::Spell>().find(selectedSpell);
// Check mana
@ -3266,10 +3270,11 @@ namespace MWWorld
If this actor is a LocalPlayer or LocalActor, get their Attack and prepare
it for sending
Set the attack details before going through with the casting, in case it's
a one use item that would get removed through the casting (like a scroll)
*/
{
cast.cast(*inv.getSelectedEnchantItem());
mwmp::Attack *localAttack = MechanicsHelper::getLocalAttack(actor);
if (localAttack)
@ -3279,6 +3284,8 @@ namespace MWWorld
localAttack->itemId = inv.getSelectedEnchantItem()->getCellRef().getRefId();
localAttack->shouldSend = true;
}
cast.cast(*inv.getSelectedEnchantItem());
}
/*
End of tes3mp addition

View file

@ -202,7 +202,7 @@ add_component_dir (openmw-mp/Packets/Object
add_component_dir (openmw-mp/Packets/Worldstate
WorldstatePacket
PacketCellCreate PacketCellReplace PacketRecordDynamic PacketWorldCollisionOverride PacketWorldMap
PacketCellCreate PacketCellReset PacketRecordDynamic PacketWorldCollisionOverride PacketWorldMap
PacketWorldRegionAuthority PacketWorldTime PacketWorldWeather
)

View file

@ -232,6 +232,8 @@ namespace mwmp
}
RakNet::RakNetGUID guid;
std::string serverPassword;
GUIMessageBox guiMessageBox;
// Track only the indexes of the attributes that have been changed,
@ -295,7 +297,6 @@ namespace mwmp
std::string birthsign;
std::string chatMessage;
CharGenState charGenState;
std::string passw;
std::string sound;
Animation animation;

View file

@ -1,5 +1,5 @@
#include "../Packets/Worldstate/PacketCellCreate.hpp"
#include "../Packets/Worldstate/PacketCellReplace.hpp"
#include "../Packets/Worldstate/PacketCellReset.hpp"
#include "../Packets/Worldstate/PacketRecordDynamic.hpp"
#include "../Packets/Worldstate/PacketWorldCollisionOverride.hpp"
#include "../Packets/Worldstate/PacketWorldMap.hpp"
@ -20,7 +20,7 @@ inline void AddPacket(mwmp::WorldstatePacketController::packets_t *packets, RakN
mwmp::WorldstatePacketController::WorldstatePacketController(RakNet::RakPeerInterface *peer)
{
AddPacket<PacketCellCreate>(&packets, peer);
AddPacket<PacketCellReplace>(&packets, peer);
AddPacket<PacketCellReset>(&packets, peer);
AddPacket<PacketRecordDynamic>(&packets, peer);
AddPacket<PacketWorldCollisionOverride>(&packets, peer);
AddPacket<PacketWorldMap>(&packets, peer);

View file

@ -104,7 +104,7 @@ enum GameMessages
ID_GAME_PREINIT,
ID_CELL_CREATE,
ID_CELL_REPLACE,
ID_CELL_RESET,
ID_RECORD_DYNAMIC,
ID_WORLD_COLLISION_OVERRIDE,
ID_WORLD_MAP,

View file

@ -1,7 +1,3 @@
//
// Created by koncord on 28.04.16.
//
#include <components/openmw-mp/NetworkMessages.hpp>
#include "PacketHandshake.hpp"
@ -17,8 +13,8 @@ void PacketHandshake::Packet(RakNet::BitStream *bs, bool send)
{
PlayerPacket::Packet(bs, send);
if (!RW(player->npc.mName, send, true, maxNameLen) ||
!RW(player->passw, send, true, maxPasswLen))
if (!RW(player->npc.mName, send, true, maxNameLength) ||
!RW(player->serverPassword, send, true, maxPasswordLength))
{
packetValid = false;
return;

View file

@ -16,8 +16,8 @@ namespace mwmp
virtual void Packet(RakNet::BitStream *bs, bool send);
const static uint32_t maxNameLen = 256;
const static uint32_t maxPasswLen = 256;
const static uint32_t maxNameLength = 256;
const static uint32_t maxPasswordLength = 256;
};
}

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