Merge branch 'main' into behavior-tree
# Conflicts: # Projects/UOContent/UOContent.csproj
This commit is contained in:
commit
380f40890c
385 changed files with 10308 additions and 6956 deletions
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
- name: Setup .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.302
|
||||
dotnet-version: 5.0.401
|
||||
- name: Build
|
||||
run: ./publish.cmd
|
||||
- name: Test
|
||||
|
|
|
|||
2
.github/workflows/create-release.yml
vendored
2
.github/workflows/create-release.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install NGBV
|
||||
uses: dotnet/nbgv@master
|
||||
id: nbgv
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,13 +2,16 @@
|
|||
/Distribution/ModernUO
|
||||
/Distribution/ModernUO.*
|
||||
/Distribution/Assemblies
|
||||
/Distribution/bsdtar
|
||||
/Distribution/Configuration/modernuo.json
|
||||
/Distribution/Configuration/email-settings.json
|
||||
/Distribution/Configuration/throttles.json
|
||||
/Distribution/Logs
|
||||
/Distribution/Archives
|
||||
/Distribution/Backups
|
||||
/Distribution/Saves
|
||||
/Distribution/docs
|
||||
/Distribution/temp
|
||||
/Distribution/*.dylib
|
||||
/Distribution/*.so
|
||||
/Distribution/*.dll
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@
|
|||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.4.220">
|
||||
<PackageReference Include="Nerdbank.GitVersioning" Condition="!Exists('packages.config')">
|
||||
<Version>3.4.240</Version>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<AdditionalFiles Include=".\Rules.ruleset" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -701,10 +701,6 @@ MetalDoor2 0x06C7 (Facing=EastCCW)
|
|||
5988 159 0
|
||||
6060 167 0
|
||||
|
||||
# Blood Moss
|
||||
Static 0x0F7B
|
||||
2678 712 0
|
||||
|
||||
# garbage
|
||||
Static 0x10F0
|
||||
5988 46 22
|
||||
|
|
|
|||
85
Distribution/Data/Professions/AOS/prof.txt
Normal file
85
Distribution/Data/Professions/AOS/prof.txt
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
Begin
|
||||
Name Paladin
|
||||
TrueName "Paladin"
|
||||
NameId 1061177
|
||||
DescId 1061227
|
||||
Desc 5
|
||||
TopLevel true
|
||||
Gump 5587
|
||||
Type Profession
|
||||
Skill Chivalry 50
|
||||
Skill Tactics 50
|
||||
Skill Wrestling 0
|
||||
Stat Str 30
|
||||
Stat Dex 25
|
||||
Stat Int 25
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Necromancer
|
||||
TrueName "Necromancer"
|
||||
NameId 1061178
|
||||
DescId 1061228
|
||||
Desc 4
|
||||
TopLevel true
|
||||
Gump 5557
|
||||
Type Profession
|
||||
Skill Necromancy 50
|
||||
Skill Swordsmanship 30
|
||||
Skill Wrestling 0
|
||||
Skill Tactics 20
|
||||
Stat Str 25
|
||||
Stat Dex 20
|
||||
Stat Int 45
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Warrior
|
||||
TrueName "Warrior"
|
||||
NameId 1061180
|
||||
DescId 1061230
|
||||
Desc 1
|
||||
TopLevel true
|
||||
Gump 5577
|
||||
Type Profession
|
||||
Skill Tactics 50
|
||||
Skill Healing 45
|
||||
Skill Swordsmanship 5
|
||||
Stat Str 35
|
||||
Stat Dex 35
|
||||
Stat Int 10
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Mage
|
||||
TrueName "Mage"
|
||||
NameId 1061179
|
||||
DescId 1061229
|
||||
Desc 2
|
||||
TopLevel true
|
||||
Gump 5569
|
||||
Type Profession
|
||||
Skill Magery 50
|
||||
Skill Meditation 50
|
||||
Skill Wrestling 0
|
||||
Stat Str 25
|
||||
Stat Dex 10
|
||||
Stat Int 45
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Blacksmith
|
||||
TrueName "Blacksmith"
|
||||
NameId 1061181
|
||||
DescId 1061231
|
||||
Desc 3
|
||||
TopLevel true
|
||||
Gump 5555
|
||||
Type Profession
|
||||
Skill Blacksmith 50
|
||||
Skill Tinkering 45
|
||||
Skill Mining 5
|
||||
Stat Str 60
|
||||
Stat Dex 10
|
||||
Stat Int 10
|
||||
End
|
||||
50
Distribution/Data/Professions/None/prof.txt
Normal file
50
Distribution/Data/Professions/None/prof.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
Begin
|
||||
Name Warrior
|
||||
TrueName "Warrior"
|
||||
NameId 1061180
|
||||
DescId 1061230
|
||||
Desc 1
|
||||
TopLevel true
|
||||
Gump 5577
|
||||
Type Profession
|
||||
Skill Tactics 50
|
||||
Skill Healing 45
|
||||
Skill Swordsmanship 5
|
||||
Stat Str 35
|
||||
Stat Dex 35
|
||||
Stat Int 10
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Mage
|
||||
TrueName "Mage"
|
||||
NameId 1061179
|
||||
DescId 1061229
|
||||
Desc 2
|
||||
TopLevel true
|
||||
Gump 5569
|
||||
Type Profession
|
||||
Skill Magery 50
|
||||
Skill Meditation 50
|
||||
Skill Wrestling 0
|
||||
Stat Str 25
|
||||
Stat Dex 10
|
||||
Stat Int 45
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Blacksmith
|
||||
TrueName "Blacksmith"
|
||||
NameId 1061181
|
||||
DescId 1061231
|
||||
Desc 3
|
||||
TopLevel true
|
||||
Gump 5555
|
||||
Type Profession
|
||||
Skill Blacksmith 50
|
||||
Skill Tinkering 45
|
||||
Skill Mining 5
|
||||
Stat Str 60
|
||||
Stat Dex 10
|
||||
Stat Int 10
|
||||
End
|
||||
125
Distribution/Data/Professions/SA/Prof.txt
Normal file
125
Distribution/Data/Professions/SA/Prof.txt
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
Begin
|
||||
Name Samurai
|
||||
TrueName "Samurai"
|
||||
NameId 1062948
|
||||
DescId 1062950
|
||||
Desc 6
|
||||
TopLevel true
|
||||
Gump 5591
|
||||
Type Profession
|
||||
Skill Bushido 30
|
||||
Skill Swordsmanship 30
|
||||
Skill Focus 30
|
||||
Skill Parrying 30
|
||||
Stat Str 40
|
||||
Stat Dex 30
|
||||
Stat Int 20
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Ninja
|
||||
TrueName "Ninja"
|
||||
NameId 1062949
|
||||
DescId 1062951
|
||||
Desc 7
|
||||
TopLevel true
|
||||
Gump 5589
|
||||
Type Profession
|
||||
Skill Ninjitsu 30
|
||||
Skill Hiding 30
|
||||
Skill Fencing 30
|
||||
Skill Stealth 30
|
||||
Stat Str 40
|
||||
Stat Dex 30
|
||||
Stat Int 20
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Paladin
|
||||
TrueName "Paladin"
|
||||
NameId 1061177
|
||||
DescId 1061227
|
||||
Desc 5
|
||||
TopLevel true
|
||||
Gump 5587
|
||||
Type Profession
|
||||
Skill Chivalry 30
|
||||
Skill Tactics 30
|
||||
Skill Focus 30
|
||||
Skill Swordsmanship 30
|
||||
Stat Str 45
|
||||
Stat Dex 20
|
||||
Stat Int 25
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Necromancer
|
||||
TrueName "Necromancer"
|
||||
NameId 1061178
|
||||
DescId 1061228
|
||||
Desc 4
|
||||
TopLevel true
|
||||
Gump 5557
|
||||
Type Profession
|
||||
Skill Necromancy 30
|
||||
Skill Spirit Speak 30
|
||||
Skill Swordsmanship 30
|
||||
Skill Meditation 30
|
||||
Stat Str 25
|
||||
Stat Dex 20
|
||||
Stat Int 45
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Warrior
|
||||
TrueName "Warrior"
|
||||
NameId 1061180
|
||||
DescId 1061230
|
||||
Desc 1
|
||||
TopLevel true
|
||||
Gump 5577
|
||||
Type Profession
|
||||
Skill Tactics 30
|
||||
Skill Healing 30
|
||||
Skill Swordsmanship 30
|
||||
Skill Anatomy 30
|
||||
Stat Str 45
|
||||
Stat Dex 35
|
||||
Stat Int 10
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Mage
|
||||
TrueName "Mage"
|
||||
NameId 1061179
|
||||
DescId 1061229
|
||||
Desc 2
|
||||
TopLevel true
|
||||
Gump 5569
|
||||
Type Profession
|
||||
Skill Magery 30
|
||||
Skill Meditation 30
|
||||
Skill Evaluate Intelligence 30
|
||||
Skill Wrestling 30
|
||||
Stat Str 25
|
||||
Stat Dex 20
|
||||
Stat Int 45
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Blacksmith
|
||||
TrueName "Blacksmith"
|
||||
NameId 1061181
|
||||
DescId 1061231
|
||||
Desc 3
|
||||
TopLevel true
|
||||
Gump 5555
|
||||
Type Profession
|
||||
Skill Blacksmith 30
|
||||
Skill Tinkering 30
|
||||
Skill Mining 30
|
||||
Skill Tailoring 30
|
||||
Stat Str 60
|
||||
Stat Dex 15
|
||||
Stat Int 15
|
||||
End
|
||||
118
Distribution/Data/Professions/SE/Prof.txt
Normal file
118
Distribution/Data/Professions/SE/Prof.txt
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
Begin
|
||||
Name Samurai
|
||||
TrueName "Samurai"
|
||||
NameId 1062948
|
||||
DescId 1062950
|
||||
Desc 6
|
||||
TopLevel true
|
||||
Gump 5591
|
||||
Type Profession
|
||||
Skill Bushido 50
|
||||
Skill Swordsmanship 50
|
||||
Skill Wrestling 0
|
||||
Stat Str 40
|
||||
Stat Dex 30
|
||||
Stat Int 10
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Ninja
|
||||
TrueName "Ninja"
|
||||
NameId 1062949
|
||||
DescId 1062951
|
||||
Desc 7
|
||||
TopLevel true
|
||||
Gump 5589
|
||||
Type Profession
|
||||
Skill Ninjitsu 50
|
||||
Skill Hiding 50
|
||||
Skill Wrestling 0
|
||||
Stat Str 40
|
||||
Stat Dex 30
|
||||
Stat Int 10
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Paladin
|
||||
TrueName "Paladin"
|
||||
NameId 1061177
|
||||
DescId 1061227
|
||||
Desc 5
|
||||
TopLevel true
|
||||
Gump 5587
|
||||
Type Profession
|
||||
Skill Chivalry 50
|
||||
Skill Tactics 50
|
||||
Skill Wrestling 0
|
||||
Stat Str 45
|
||||
Stat Dex 20
|
||||
Stat Int 15
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Necromancer
|
||||
TrueName "Necromancer"
|
||||
NameId 1061178
|
||||
DescId 1061228
|
||||
Desc 4
|
||||
TopLevel true
|
||||
Gump 5557
|
||||
Type Profession
|
||||
Skill Necromancy 50
|
||||
Skill Swordsmanship 30
|
||||
Skill Tactics 20
|
||||
Stat Str 25
|
||||
Stat Dex 20
|
||||
Stat Int 35
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Warrior
|
||||
TrueName "Warrior"
|
||||
NameId 1061180
|
||||
DescId 1061230
|
||||
Desc 1
|
||||
TopLevel true
|
||||
Gump 5577
|
||||
Type Profession
|
||||
Skill Tactics 50
|
||||
Skill Healing 45
|
||||
Skill Swordsmanship 5
|
||||
Stat Str 35
|
||||
Stat Dex 35
|
||||
Stat Int 10
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Mage
|
||||
TrueName "Mage"
|
||||
NameId 1061179
|
||||
DescId 1061229
|
||||
Desc 2
|
||||
TopLevel true
|
||||
Gump 5569
|
||||
Type Profession
|
||||
Skill Magery 50
|
||||
Skill Meditation 50
|
||||
Skill Wrestling 0
|
||||
Stat Str 25
|
||||
Stat Dex 10
|
||||
Stat Int 45
|
||||
End
|
||||
|
||||
Begin
|
||||
Name Blacksmith
|
||||
TrueName "Blacksmith"
|
||||
NameId 1061181
|
||||
DescId 1061231
|
||||
Desc 3
|
||||
TopLevel true
|
||||
Gump 5555
|
||||
Type Profession
|
||||
Skill Blacksmith 50
|
||||
Skill Tinkering 45
|
||||
Skill Mining 5
|
||||
Stat Str 60
|
||||
Stat Dex 10
|
||||
Stat Int 10
|
||||
End
|
||||
58
Distribution/Data/Professions/all-profession-skill-names.txt
Normal file
58
Distribution/Data/Professions/all-profession-skill-names.txt
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
Alchemy,
|
||||
Anatomy,
|
||||
AnimalLore,
|
||||
ItemID,
|
||||
ArmsLore,
|
||||
Parrying,
|
||||
Begging,
|
||||
Blacksmith,
|
||||
Bowcraft,
|
||||
Peacemaking,
|
||||
Camping,
|
||||
Carpentry,
|
||||
Cartography,
|
||||
Cooking,
|
||||
DetectHidden,
|
||||
Enticement,
|
||||
EvaluateIntelligence,
|
||||
Healing,
|
||||
Fishing,
|
||||
ForensicEvaluation,
|
||||
Herding,
|
||||
Hiding,
|
||||
Provocation,
|
||||
Inscription,
|
||||
Lockpicking,
|
||||
Magery,
|
||||
ResistingSpells,
|
||||
Tactics,
|
||||
Snooping,
|
||||
Musicianship,
|
||||
Poisoning,
|
||||
Archery,
|
||||
SpiritSpeak,
|
||||
Stealing,
|
||||
Tailoring,
|
||||
AnimalTaming,
|
||||
TasteIdentification,
|
||||
Tinkering,
|
||||
Tracking,
|
||||
Veterinary,
|
||||
Swordsmanship,
|
||||
MaceFighting,
|
||||
Fencing,
|
||||
Wrestling,
|
||||
Lumberjacking,
|
||||
Mining,
|
||||
Meditation,
|
||||
Stealth,
|
||||
Disarm,
|
||||
Necromancy,
|
||||
Focus,
|
||||
Chivalry,
|
||||
Bushido,
|
||||
Ninjitsu,
|
||||
Spellweaving,
|
||||
Mysticism,
|
||||
Imbuing,
|
||||
Throwing
|
||||
794
Distribution/Data/expansion.json
Normal file
794
Distribution/Data/expansion.json
Normal file
|
|
@ -0,0 +1,794 @@
|
|||
[
|
||||
{
|
||||
"Name": "None",
|
||||
"ClientVersion": null,
|
||||
"ClientFlags": null,
|
||||
"FeatureFlags": {
|
||||
"None": true,
|
||||
"T2A": false,
|
||||
"UOR": false,
|
||||
"UOTD": false,
|
||||
"LBR": false,
|
||||
"AOS": false,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"EigthAge": false,
|
||||
"NinthAge": false,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": true,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": false,
|
||||
"SlotLimit": false,
|
||||
"AOS": false,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": true,
|
||||
"AOS": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Crystal": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "The Second Age",
|
||||
"ClientVersion": null,
|
||||
"ClientFlags": "Felucca",
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": false,
|
||||
"UOTD": false,
|
||||
"LBR": false,
|
||||
"AOS": false,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"EigthAge": false,
|
||||
"NinthAge": false,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": false,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": true,
|
||||
"AOS": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Crystal": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Renaissance",
|
||||
"ClientVersion": null,
|
||||
"ClientFlags": "Trammel",
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": true,
|
||||
"UOTD": false,
|
||||
"LBR": false,
|
||||
"AOS": false,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"EigthAge": false,
|
||||
"NinthAge": false,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": false,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": true,
|
||||
"AOS": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Crystal": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Third Dawn",
|
||||
"ClientVersion": null,
|
||||
"ClientFlags": "Ilshenar",
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": true,
|
||||
"UOTD": true,
|
||||
"LBR": false,
|
||||
"AOS": false,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"EigthAge": false,
|
||||
"NinthAge": false,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": false,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": true,
|
||||
"AOS": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Crystal": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Blackthorn's Revenge",
|
||||
"ClientVersion": null,
|
||||
"ClientFlags": "Ilshenar",
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": true,
|
||||
"UOTD": true,
|
||||
"LBR": true,
|
||||
"AOS": false,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"EigthAge": false,
|
||||
"NinthAge": false,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": false,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": true,
|
||||
"AOS": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Crystal": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Age of Shadows",
|
||||
"ClientVersion": null,
|
||||
"ClientFlags": "Malas",
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": true,
|
||||
"UOTD": true,
|
||||
"LBR": true,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"EigthAge": false,
|
||||
"NinthAge": false,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": true,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": false,
|
||||
"AOS": true,
|
||||
"SE": false,
|
||||
"ML": false,
|
||||
"Crystal": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Samurai Empire",
|
||||
"ClientVersion": null,
|
||||
"ClientFlags": "Tokuno",
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": true,
|
||||
"UOTD": true,
|
||||
"LBR": true,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": false,
|
||||
"EigthAge": false,
|
||||
"NinthAge": false,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": true,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": false,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": false,
|
||||
"AOS": true,
|
||||
"SE": true,
|
||||
"ML": false,
|
||||
"Crystal": false,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Mondain's Legacy",
|
||||
"ClientVersion": "5.0.0a",
|
||||
"ClientFlags": null,
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": true,
|
||||
"UOTD": true,
|
||||
"LBR": true,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"EigthAge": false,
|
||||
"NinthAge": true,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": true,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": false,
|
||||
"AOS": true,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"Crystal": true,
|
||||
"SA": false,
|
||||
"HS": false,
|
||||
"Gothic": false,
|
||||
"Rustic": false,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Stygian Abyss",
|
||||
"ClientVersion": null,
|
||||
"ClientFlags": "TerMur",
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": true,
|
||||
"UOTD": true,
|
||||
"LBR": true,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"EigthAge": false,
|
||||
"NinthAge": true,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": true,
|
||||
"SA": true,
|
||||
"HS": false,
|
||||
"Gothic": true,
|
||||
"Rustic": true,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": false,
|
||||
"AOS": true,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"Crystal": true,
|
||||
"SA": true,
|
||||
"HS": false,
|
||||
"Gothic": true,
|
||||
"Rustic": true,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "High Seas",
|
||||
"ClientVersion": "7.0.9.0",
|
||||
"ClientFlags": null,
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": true,
|
||||
"UOTD": true,
|
||||
"LBR": true,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"EigthAge": false,
|
||||
"NinthAge": true,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": true,
|
||||
"SA": true,
|
||||
"HS": true,
|
||||
"Gothic": true,
|
||||
"Rustic": true,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": false,
|
||||
"AOS": true,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"Crystal": true,
|
||||
"SA": true,
|
||||
"HS": true,
|
||||
"Gothic": true,
|
||||
"Rustic": true,
|
||||
"Jungle": false,
|
||||
"Shadowguard": false,
|
||||
"TOL": false,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Time of Legends",
|
||||
"ClientVersion": "7.0.45.65",
|
||||
"ClientFlags": null,
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": true,
|
||||
"UOTD": true,
|
||||
"LBR": true,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"EigthAge": false,
|
||||
"NinthAge": true,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": true,
|
||||
"SA": true,
|
||||
"HS": true,
|
||||
"Gothic": true,
|
||||
"Rustic": true,
|
||||
"Jungle": true,
|
||||
"Shadowguard": true,
|
||||
"TOL": true,
|
||||
"EJ": false
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": false,
|
||||
"AOS": true,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"Crystal": true,
|
||||
"SA": true,
|
||||
"HS": true,
|
||||
"Gothic": true,
|
||||
"Rustic": true,
|
||||
"Jungle": true,
|
||||
"Shadowguard": true,
|
||||
"TOL": true,
|
||||
"EJ": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Endless Journey",
|
||||
"ClientVersion": "7.0.61.0",
|
||||
"ClientFlags": null,
|
||||
"FeatureFlags": {
|
||||
"None": false,
|
||||
"T2A": true,
|
||||
"UOR": true,
|
||||
"UOTD": true,
|
||||
"LBR": true,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"EigthAge": false,
|
||||
"NinthAge": true,
|
||||
"TenthAge": false,
|
||||
"IncreasedStorage": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"RoleplayFaces": false,
|
||||
"TrialAccount": false,
|
||||
"LiveAccount": true,
|
||||
"SA": true,
|
||||
"HS": true,
|
||||
"Gothic": true,
|
||||
"Rustic": true,
|
||||
"Jungle": true,
|
||||
"Shadowguard": true,
|
||||
"TOL": true,
|
||||
"EJ": true
|
||||
},
|
||||
"CharacterListFlags": {
|
||||
"None": false,
|
||||
"Unk1": false,
|
||||
"OverwriteConfigButton": false,
|
||||
"OneCharacterSlot": false,
|
||||
"ContextMenus": true,
|
||||
"SlotLimit": false,
|
||||
"AOS": true,
|
||||
"SixthCharacterSlot": false,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"Unk2": false,
|
||||
"UO3DClientType": false,
|
||||
"Unk3": false,
|
||||
"SeventhCharacterSlot": false,
|
||||
"Unk4": false,
|
||||
"NewMovementSystem": false,
|
||||
"NewFeluccaAreas": false
|
||||
},
|
||||
"HousingFlags": {
|
||||
"None": false,
|
||||
"AOS": true,
|
||||
"SE": true,
|
||||
"ML": true,
|
||||
"Crystal": true,
|
||||
"SA": true,
|
||||
"HS": true,
|
||||
"Gothic": true,
|
||||
"Rustic": true,
|
||||
"Jungle": true,
|
||||
"Shadowguard": true,
|
||||
"TOL": true,
|
||||
"EJ": true
|
||||
}
|
||||
}
|
||||
]
|
||||
1157
Distribution/Data/timezones.json
Normal file
1157
Distribution/Data/timezones.json
Normal file
File diff suppressed because it is too large
Load diff
12
FAQ.md
12
FAQ.md
|
|
@ -1,15 +1,21 @@
|
|||
# Frequently Asked Questions
|
||||
- [Why do I have thousands of compile errors](#why-do-i-have-thousands-of-compile-errors)
|
||||
- [Where do I go to run ModernUO?](#where-do-i-go-to-run-modernuo)
|
||||
- [I don't see my scripts compiling, what happened?](#i-dont-see-my-scripts-compiling-what-happened)
|
||||
- [Why can't I run ModernUO on a 32-bit operating system?](#why-cant-i-run-modernuo-on-a-32-bit-operating-system)
|
||||
|
||||
## Why do I have thousands of compile errors?
|
||||
ModernUO uses a new technique called source generation. Most likely the compile errors are related to that.
|
||||
To resolve the compile errors make sure you are using Visual Studio 2019 v16.10+, or Rider 2021.2+ for development and the newest .NET available.
|
||||
Specifically for Visual Studio, you must build the entire solution once, restart Visual Studio entirely, and then build again.
|
||||
This is required every time code changes in the `SerializationGenerator` or `SerializationSchemaGenerator` projects.
|
||||
|
||||
## Where do I go to run ModernUO?
|
||||
Everything is run from the `Distribution` directory.
|
||||
This folder is portable, so it can be moved to the production server for deployments.
|
||||
|
||||
## I don't see my scripts compiling, what happened?
|
||||
NET Core does not support dynamic compiling. Scripts are now compiled separately and moved to the `Distribution/Assemblies` directory.
|
||||
This also allows content to be divided into `modules` that can be included by dropping in the DLL.
|
||||
.NET 5 does not support dynamic compiling newer versions of C#. Scripts are now compiled separately and moved to the `Distribution/Assemblies` directory.
|
||||
|
||||
## Why can't I run ModernUO on a 32-bit operating system?
|
||||
ModernUO is optimized for performance and modern hardware.
|
||||
ModernUO is optimized for performance and modern hardware. For this reason we no longer support 32-bit operating systems
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<SkipLocalsInitiAttribute>true</SkipLocalsInitiAttribute>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<ProjectReference Include="..\Server\Server.csproj" />
|
||||
<ProjectReference Include="..\UOContent\UOContent.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ namespace SerializationGenerator
|
|||
) != null
|
||||
) : null;
|
||||
|
||||
var serializablePropertySet = new SortedSet<SerializableProperty>(new SerializablePropertyComparer());
|
||||
var serializablePropertySet = new SortedDictionary<SerializableProperty, ISymbol>(new SerializablePropertyComparer());
|
||||
|
||||
foreach (var fieldOrPropertySymbol in fieldsAndProperties)
|
||||
{
|
||||
|
|
@ -267,10 +267,16 @@ namespace SerializationGenerator
|
|||
serializableFieldSaveFlagMethods
|
||||
);
|
||||
|
||||
serializablePropertySet.Add(serializableProperty);
|
||||
serializablePropertySet.Add(serializableProperty, fieldOrPropertySymbol);
|
||||
}
|
||||
|
||||
var serializableProperties = serializablePropertySet.ToImmutableArray();
|
||||
var serializableFields = serializablePropertySet.Keys.ToImmutableArray();
|
||||
var serializableProperties = serializablePropertySet.Select(
|
||||
kvp => kvp.Key with
|
||||
{
|
||||
Name = (kvp.Value as IFieldSymbol)?.GetPropertyName() ?? ((IPropertySymbol)kvp.Value).Name
|
||||
}
|
||||
).ToImmutableArray();
|
||||
|
||||
// If we are not inheriting ISerializable, then we need to define some stuff
|
||||
if (!(isOverride || embedded))
|
||||
|
|
@ -323,6 +329,7 @@ namespace SerializationGenerator
|
|||
indent,
|
||||
isOverride,
|
||||
encodedVersion,
|
||||
serializableFields,
|
||||
serializableProperties,
|
||||
serializableFieldSaveFlags
|
||||
);
|
||||
|
|
@ -337,6 +344,7 @@ namespace SerializationGenerator
|
|||
version,
|
||||
encodedVersion,
|
||||
migrations,
|
||||
serializableFields,
|
||||
serializableProperties,
|
||||
parentFieldOrProperty,
|
||||
serializableFieldSaveFlags
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ namespace SerializationGenerator
|
|||
int version,
|
||||
bool encodedVersion,
|
||||
ImmutableArray<SerializableMetadata> migrations,
|
||||
ImmutableArray<SerializableProperty> fields,
|
||||
ImmutableArray<SerializableProperty> properties,
|
||||
ISymbol parentFieldOrProperty,
|
||||
SortedDictionary<int, SerializableFieldSaveFlagMethods> serializableFieldSaveFlagMethodsDictionary
|
||||
|
|
@ -124,11 +125,12 @@ namespace SerializationGenerator
|
|||
source.AppendLine($"{bodyIndent}var saveFlags = reader.ReadEnum<SaveFlag>();");
|
||||
}
|
||||
|
||||
foreach (var property in properties)
|
||||
for (var i = 0; i < properties.Length; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
var property = properties[i];
|
||||
var rule = SerializableMigrationRulesEngine.Rules[property.Rule];
|
||||
|
||||
|
||||
if (serializableFieldSaveFlagMethodsDictionary.TryGetValue(
|
||||
property.Order,
|
||||
out var serializableFieldSaveFlagMethods
|
||||
|
|
@ -138,7 +140,7 @@ namespace SerializationGenerator
|
|||
// Special case
|
||||
if (property.Type == "bool")
|
||||
{
|
||||
source.AppendLine($"{bodyIndent}{property.Name} = (saveFlags & SaveFlag.{property.Name}) != 0;");
|
||||
source.AppendLine($"{bodyIndent}{field.Name} = (saveFlags & SaveFlag.{property.Name}) != 0;");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -146,16 +148,22 @@ namespace SerializationGenerator
|
|||
rule.GenerateDeserializationMethod(
|
||||
source,
|
||||
innerIndent,
|
||||
property,
|
||||
field,
|
||||
parentFieldOrProperty?.Name ?? "this"
|
||||
);
|
||||
(rule as IPostDeserializeMethod)?.PostDeserializeMethod(source, innerIndent, property, compilation, classSymbol);
|
||||
(rule as IPostDeserializeMethod)?.PostDeserializeMethod(
|
||||
source,
|
||||
innerIndent,
|
||||
field,
|
||||
compilation,
|
||||
classSymbol
|
||||
);
|
||||
|
||||
if (serializableFieldSaveFlagMethods.GetFieldDefaultValue != null)
|
||||
{
|
||||
source.AppendLine($"{bodyIndent}}}\n{bodyIndent}else\n{bodyIndent}{{");
|
||||
source.AppendLine(
|
||||
$"{bodyIndent} {property.Name} = {serializableFieldSaveFlagMethods.GetFieldDefaultValue.Name}();"
|
||||
$"{bodyIndent} {field.Name} = {serializableFieldSaveFlagMethods.GetFieldDefaultValue.Name}();"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -168,10 +176,16 @@ namespace SerializationGenerator
|
|||
rule.GenerateDeserializationMethod(
|
||||
source,
|
||||
bodyIndent,
|
||||
property,
|
||||
field,
|
||||
parentFieldOrProperty?.Name ?? "this"
|
||||
);
|
||||
(rule as IPostDeserializeMethod)?.PostDeserializeMethod(source, bodyIndent, property, compilation, classSymbol);
|
||||
(rule as IPostDeserializeMethod)?.PostDeserializeMethod(
|
||||
source,
|
||||
bodyIndent,
|
||||
field,
|
||||
compilation,
|
||||
classSymbol
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ namespace SerializationGenerator
|
|||
string indent,
|
||||
bool isOverride,
|
||||
bool encodedVersion,
|
||||
ImmutableArray<SerializableProperty> fields,
|
||||
ImmutableArray<SerializableProperty> properties,
|
||||
SortedDictionary<int, SerializableFieldSaveFlagMethods> serializableFieldSaveFlagMethodsDictionary
|
||||
)
|
||||
|
|
@ -74,30 +75,34 @@ namespace SerializationGenerator
|
|||
source.AppendLine($"{bodyIndent}writer.WriteEnum(saveFlags);");
|
||||
}
|
||||
|
||||
foreach (var property in properties)
|
||||
for (var i = 0; i < properties.Length; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
var property = properties[i];
|
||||
if (serializableFieldSaveFlagMethodsDictionary.ContainsKey(property.Order))
|
||||
{
|
||||
// Special case
|
||||
if (property.Type != "bool")
|
||||
{
|
||||
source.AppendLine($"\n{bodyIndent}if ((saveFlags & SaveFlag.{property.Name}) != 0)\n{bodyIndent}{{");
|
||||
SerializableMigrationRulesEngine.Rules[property.Rule].GenerateSerializationMethod(
|
||||
source,
|
||||
innerIndent,
|
||||
property
|
||||
);
|
||||
SerializableMigrationRulesEngine.Rules[property.Rule]
|
||||
.GenerateSerializationMethod(
|
||||
source,
|
||||
innerIndent,
|
||||
field
|
||||
);
|
||||
source.AppendLine($"{bodyIndent}}}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
source.AppendLine();
|
||||
SerializableMigrationRulesEngine.Rules[property.Rule].GenerateSerializationMethod(
|
||||
source,
|
||||
bodyIndent,
|
||||
property
|
||||
);
|
||||
SerializableMigrationRulesEngine.Rules[property.Rule]
|
||||
.GenerateSerializationMethod(
|
||||
source,
|
||||
bodyIndent,
|
||||
field
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace SerializableMigration
|
|||
|
||||
if (fieldOrPropertySymbol is IFieldSymbol fieldSymbol)
|
||||
{
|
||||
propertyName = fieldSymbol.GetPropertyName();
|
||||
propertyName = fieldSymbol.Name;
|
||||
propertyType = fieldSymbol.Type;
|
||||
}
|
||||
else if (fieldOrPropertySymbol is IPropertySymbol propertySymbol)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.9.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.11.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.11.10" GeneratePathProperty="true" PrivateAssets="all" />
|
||||
<PackageReference Include="System.Text.Json" Version="5.0.0" GeneratePathProperty="true" PrivateAssets="all" />
|
||||
<PackageReference Include="System.Text.Encodings.Web" Version="5.0.0" GeneratePathProperty="true" PrivateAssets="all" />
|
||||
|
|
|
|||
5
Projects/SerializationSchemaGenerator/SerializationSchemaGenerator.csproj
Normal file → Executable file
5
Projects/SerializationSchemaGenerator/SerializationSchemaGenerator.csproj
Normal file → Executable file
|
|
@ -14,8 +14,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Locator" Version="1.4.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.10.0.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.11.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.11.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Server.Tests
|
||||
{
|
||||
|
|
@ -7,8 +8,8 @@ namespace Server.Tests
|
|||
// Global setup
|
||||
static ServerFixture()
|
||||
{
|
||||
Core.Assembly = Assembly.GetExecutingAssembly();
|
||||
Core.LoopContext = new EventLoopContext();
|
||||
|
||||
Core.Expansion = Expansion.EJ;
|
||||
|
||||
// Load Configurations
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using Moq;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Tests.Network
|
||||
|
|
@ -11,18 +9,8 @@ namespace Server.Tests.Network
|
|||
public static Span<byte> Compile(this Packet p) =>
|
||||
p.Compile(false, out var length).AsSpan(0, length);
|
||||
|
||||
public static NetState CreateTestNetState()
|
||||
{
|
||||
var socket = new Mock<ISocket>();
|
||||
socket
|
||||
.Setup(s => s.SendAsync(It.IsAny<IList<ArraySegment<byte>>>(), SocketFlags.None))
|
||||
.ReturnsAsync(() => 0);
|
||||
|
||||
socket
|
||||
.Setup(s => s.ReceiveAsync(It.IsAny<IList<ArraySegment<byte>>>(), SocketFlags.None))
|
||||
.ReturnsAsync(() => 0);
|
||||
|
||||
return new NetState(socket.Object);
|
||||
}
|
||||
public static NetState CreateTestNetState() => new(
|
||||
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,19 @@
|
|||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="Zlib.Bindings" Version="1.5.0" />
|
||||
<ProjectReference Include="..\Server\Server.csproj" />
|
||||
<ProjectReference Include="..\UOContent\UOContent.csproj" />
|
||||
<DataFiles Include="$(SolutionDir)\Distribution\Data\**" />
|
||||
<DataFiles Update="..\..\Distribution\Data\Professions\LBR\prof.txt">
|
||||
<Link>Data\Professions\LBR\prof.txt</Link>
|
||||
</DataFiles>
|
||||
</ItemGroup>
|
||||
<Target Name="CopyData" AfterTargets="AfterBuild">
|
||||
<Copy SourceFiles="@(DataFiles)" DestinationFolder="$(OutDir)\Data\%(RecursiveDir)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Server.Tests.Network
|
|||
[Fact]
|
||||
public void TestGlobalLightLevel()
|
||||
{
|
||||
byte lightLevel = 5;
|
||||
const byte lightLevel = 5;
|
||||
var expected = new GlobalLightLevel(lightLevel).Compile();
|
||||
|
||||
var ns = PacketTestUtilities.CreateTestNetState();
|
||||
|
|
|
|||
|
|
@ -173,14 +173,6 @@ namespace Server
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string EnsureDirectory(string dir)
|
||||
{
|
||||
var path = Path.Combine(Core.BaseDirectory, dir);
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public class TypeCache
|
||||
|
|
|
|||
24
Projects/Server/Comparers/DescendingComparer.cs
Normal file
24
Projects/Server/Comparers/DescendingComparer.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: DescendingComparer.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class DescendingComparer<T> : IComparer<T> where T : IComparable<T> {
|
||||
public int Compare(T x, T y) => y?.CompareTo(x) ?? 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -169,6 +169,17 @@ namespace Server
|
|||
return value;
|
||||
}
|
||||
|
||||
public static void SetSetting(string key, TimeSpan value) => SetSetting(key, value.ToString());
|
||||
|
||||
public static void SetSetting(string key, int value) => SetSetting(key, value.ToString());
|
||||
|
||||
public static void SetSetting(string key, long value) => SetSetting(key, value.ToString());
|
||||
|
||||
public static void SetSetting(string key, bool value) => SetSetting(key, value.ToString());
|
||||
|
||||
public static void SetSetting<T>(string key, T value) where T : struct, Enum =>
|
||||
SetSetting(key, value.ToString());
|
||||
|
||||
public static void SetSetting(string key, string value)
|
||||
{
|
||||
m_Settings.Settings[key] = value;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ namespace Server
|
|||
evt.WaitOne();
|
||||
}
|
||||
|
||||
public int ExecuteTasks()
|
||||
public void ExecuteTasks()
|
||||
{
|
||||
if (Thread.CurrentThread != _mainThread)
|
||||
{
|
||||
|
|
@ -66,15 +66,11 @@ namespace Server
|
|||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (!_queue.TryDequeue(out var a))
|
||||
if (_queue.TryDequeue(out var a))
|
||||
{
|
||||
return count;
|
||||
a();
|
||||
}
|
||||
|
||||
a();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ namespace Server
|
|||
public class CharacterCreatedEventArgs
|
||||
{
|
||||
public CharacterCreatedEventArgs(
|
||||
NetState state, IAccount a, string name, bool female, int hue, int str, int dex,
|
||||
int intel, CityInfo city, SkillNameValue[] skills, int shirtHue, int pantsHue, int hairID, int hairHue,
|
||||
int beardID, int beardHue, int profession, Race race
|
||||
NetState state, IAccount a, string name, bool female, int hue, StatNameValue[] stats, CityInfo city,
|
||||
SkillNameValue[] skills, int shirtHue, int pantsHue, int hairID, int hairHue, int beardID, int beardHue,
|
||||
int profession, Race race
|
||||
)
|
||||
{
|
||||
State = state;
|
||||
|
|
@ -33,9 +33,7 @@ namespace Server
|
|||
Name = name;
|
||||
Female = female;
|
||||
Hue = hue;
|
||||
Str = str;
|
||||
Dex = dex;
|
||||
Int = intel;
|
||||
Stats = stats;
|
||||
City = city;
|
||||
Skills = skills;
|
||||
ShirtHue = shirtHue;
|
||||
|
|
@ -60,11 +58,7 @@ namespace Server
|
|||
|
||||
public int Hue { get; }
|
||||
|
||||
public int Str { get; }
|
||||
|
||||
public int Dex { get; }
|
||||
|
||||
public int Int { get; }
|
||||
public StatNameValue[] Stats { get; }
|
||||
|
||||
public CityInfo City { get; }
|
||||
|
||||
|
|
@ -87,18 +81,8 @@ namespace Server
|
|||
public Race Race { get; }
|
||||
}
|
||||
|
||||
public struct SkillNameValue
|
||||
{
|
||||
public SkillName Name { get; }
|
||||
|
||||
public int Value { get; }
|
||||
|
||||
public SkillNameValue(SkillName name, int value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
public readonly record struct SkillNameValue(SkillName Name, int Value);
|
||||
public readonly record struct StatNameValue(StatType Name, int Value);
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,23 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ExpansionInfo.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using Server.Json;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
|
|
@ -139,107 +158,63 @@ namespace Server
|
|||
|
||||
public class ExpansionInfo
|
||||
{
|
||||
public static string GetEraFolder(string parentDirectory)
|
||||
{
|
||||
var expansion = Core.Expansion;
|
||||
var folders = Directory.GetDirectories(
|
||||
parentDirectory,
|
||||
"*",
|
||||
new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }
|
||||
);
|
||||
|
||||
while (expansion-- >= 0)
|
||||
{
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
var di = new DirectoryInfo(folder);
|
||||
if (di.Name.InsensitiveEquals(expansion.ToString()))
|
||||
{
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static ExpansionInfo()
|
||||
{
|
||||
Table = new[]
|
||||
var path = Path.Combine(Core.BaseDirectory, "Data/expansion.json");
|
||||
var expansions = JsonConfig.Deserialize<List<ExpansionConfig>>(path);
|
||||
|
||||
Table = new ExpansionInfo[expansions.Count];
|
||||
|
||||
for (var i = 0; i < expansions.Count; i++)
|
||||
{
|
||||
new ExpansionInfo(
|
||||
0,
|
||||
"None",
|
||||
ClientFlags.None,
|
||||
FeatureFlags.ExpansionNone,
|
||||
CharacterListFlags.ExpansionNone,
|
||||
HousingFlags.None
|
||||
),
|
||||
new ExpansionInfo(
|
||||
1,
|
||||
"The Second Age",
|
||||
ClientFlags.Felucca,
|
||||
FeatureFlags.ExpansionT2A,
|
||||
CharacterListFlags.ExpansionT2A,
|
||||
HousingFlags.None
|
||||
),
|
||||
new ExpansionInfo(
|
||||
2,
|
||||
"Renaissance",
|
||||
ClientFlags.Trammel,
|
||||
FeatureFlags.ExpansionUOR,
|
||||
CharacterListFlags.ExpansionUOR,
|
||||
HousingFlags.None
|
||||
),
|
||||
new ExpansionInfo(
|
||||
3,
|
||||
"Third Dawn",
|
||||
ClientFlags.Ilshenar,
|
||||
FeatureFlags.ExpansionUOTD,
|
||||
CharacterListFlags.ExpansionUOTD,
|
||||
HousingFlags.None
|
||||
),
|
||||
new ExpansionInfo(
|
||||
4,
|
||||
"Blackthorn's Revenge",
|
||||
ClientFlags.Ilshenar,
|
||||
FeatureFlags.ExpansionLBR,
|
||||
CharacterListFlags.ExpansionLBR,
|
||||
HousingFlags.None
|
||||
),
|
||||
new ExpansionInfo(
|
||||
5,
|
||||
"Age of Shadows",
|
||||
ClientFlags.Malas,
|
||||
FeatureFlags.ExpansionAOS,
|
||||
CharacterListFlags.ExpansionAOS,
|
||||
HousingFlags.HousingAOS
|
||||
),
|
||||
new ExpansionInfo(
|
||||
6,
|
||||
"Samurai Empire",
|
||||
ClientFlags.Tokuno,
|
||||
FeatureFlags.ExpansionSE,
|
||||
CharacterListFlags.ExpansionSE,
|
||||
HousingFlags.HousingSE
|
||||
),
|
||||
new ExpansionInfo(
|
||||
7,
|
||||
"Mondain's Legacy",
|
||||
new ClientVersion("5.0.0a"),
|
||||
FeatureFlags.ExpansionML,
|
||||
CharacterListFlags.ExpansionML,
|
||||
HousingFlags.HousingML
|
||||
),
|
||||
new ExpansionInfo(
|
||||
8,
|
||||
"Stygian Abyss",
|
||||
ClientFlags.TerMur,
|
||||
FeatureFlags.ExpansionSA,
|
||||
CharacterListFlags.ExpansionSA,
|
||||
HousingFlags.HousingSA
|
||||
),
|
||||
new ExpansionInfo(
|
||||
9,
|
||||
"High Seas",
|
||||
new ClientVersion("7.0.9.0"),
|
||||
FeatureFlags.ExpansionHS,
|
||||
CharacterListFlags.ExpansionHS,
|
||||
HousingFlags.HousingHS
|
||||
),
|
||||
new ExpansionInfo(
|
||||
10,
|
||||
"Time of Legends",
|
||||
new ClientVersion("7.0.45.65"),
|
||||
FeatureFlags.ExpansionTOL,
|
||||
CharacterListFlags.ExpansionTOL,
|
||||
HousingFlags.HousingTOL
|
||||
),
|
||||
new ExpansionInfo(
|
||||
11,
|
||||
"Endless Journey",
|
||||
new ClientVersion("7.0.61.0"),
|
||||
FeatureFlags.ExpansionEJ,
|
||||
CharacterListFlags.ExpansionEJ,
|
||||
HousingFlags.HousingEJ
|
||||
)
|
||||
};
|
||||
var expansion = expansions[i];
|
||||
if (expansion.ClientVersion != null)
|
||||
{
|
||||
Table[i] = new ExpansionInfo(
|
||||
i,
|
||||
expansion.Name,
|
||||
expansion.ClientVersion,
|
||||
expansion.FeatureFlags,
|
||||
expansion.CharacterListFlags,
|
||||
expansion.HousingFlags
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Table[i] = new ExpansionInfo(
|
||||
i,
|
||||
expansion.Name,
|
||||
expansion.ClientFlags ?? ClientFlags.None,
|
||||
expansion.FeatureFlags,
|
||||
expansion.CharacterListFlags,
|
||||
expansion.HousingFlags
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ExpansionInfo(
|
||||
|
|
@ -331,4 +306,22 @@ namespace Server
|
|||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
||||
public record ExpansionConfig
|
||||
{
|
||||
public string Name { get; init; }
|
||||
|
||||
public ClientVersion? ClientVersion { get; init; }
|
||||
|
||||
public ClientFlags? ClientFlags { get; init; }
|
||||
|
||||
[JsonConverter(typeof(FlagsConverter<FeatureFlags>))]
|
||||
public FeatureFlags FeatureFlags { get; init; }
|
||||
|
||||
[JsonConverter(typeof(FlagsConverter<CharacterListFlags>))]
|
||||
public CharacterListFlags CharacterListFlags { get; init; }
|
||||
|
||||
[JsonConverter(typeof(FlagsConverter<HousingFlags>))]
|
||||
public HousingFlags HousingFlags { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,13 +63,21 @@ namespace Server.Guilds
|
|||
[CommandProperty(AccessLevel.Counselor)]
|
||||
public Serial Serial { get; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, readOnly: true)]
|
||||
public DateTime Created { get; set; } = Core.Now;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
DateTime ISerializable.LastSerialized { get; set; } = Core.Now;
|
||||
|
||||
long ISerializable.SavePosition { get; set; } = -1;
|
||||
|
||||
BufferWriter ISerializable.SaveBuffer { get; set; }
|
||||
|
||||
public int TypeRef { get; private set; }
|
||||
|
||||
public abstract void BeforeSerialize();
|
||||
public abstract void Serialize(IGenericWriter writer);
|
||||
|
||||
public abstract void Deserialize(IGenericReader reader);
|
||||
public abstract void OnDelete(Mobile mob);
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ namespace Server
|
|||
{
|
||||
}
|
||||
|
||||
DateTime ISerializable.Created { get; set; } = Core.Now;
|
||||
|
||||
DateTime ISerializable.LastSerialized { get; set; } = DateTime.MaxValue;
|
||||
|
||||
long ISerializable.SavePosition { get; set; } = -1;
|
||||
|
||||
BufferWriter ISerializable.SaveBuffer { get; set; }
|
||||
|
|
@ -103,6 +107,10 @@ namespace Server
|
|||
&& p.Y >= Location.m_Y - range
|
||||
&& p.Y <= Location.m_Y + range;
|
||||
|
||||
public void BeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
public void Deserialize(IGenericReader reader)
|
||||
{
|
||||
// Should not actually be saved
|
||||
|
|
|
|||
|
|
@ -763,7 +763,7 @@ namespace Server.Items
|
|||
{
|
||||
if (Core.ML)
|
||||
{
|
||||
if (ParentsContain<BankBox>()) // Root Parent is the Mobile. Parent could be another containter.
|
||||
if (ParentsContain<BankBox>()) // Root Parent is the Mobile. Parent could be another container.
|
||||
{
|
||||
list.Add(
|
||||
1073841, // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~ stones
|
||||
|
|
|
|||
|
|
@ -771,6 +771,12 @@ namespace Server
|
|||
AddNameProperties(list);
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, readOnly: true)]
|
||||
public DateTime Created { get; set; } = Core.Now;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
DateTime ISerializable.LastSerialized { get; set; } = Core.Now;
|
||||
|
||||
long ISerializable.SavePosition { get; set; } = -1;
|
||||
|
||||
BufferWriter ISerializable.SaveBuffer { get; set; }
|
||||
|
|
@ -790,12 +796,12 @@ namespace Server
|
|||
|
||||
if (x != 0 || y != 0 || z != 0)
|
||||
{
|
||||
if (x >= short.MinValue && x <= short.MaxValue && y >= short.MinValue && y <= short.MaxValue &&
|
||||
z >= sbyte.MinValue && z <= sbyte.MaxValue)
|
||||
if (x is >= short.MinValue and <= short.MaxValue && y is >= short.MinValue and <= short.MaxValue &&
|
||||
z is >= sbyte.MinValue and <= sbyte.MaxValue)
|
||||
{
|
||||
if (x != 0 || y != 0)
|
||||
{
|
||||
if (x >= byte.MinValue && x <= byte.MaxValue && y >= byte.MinValue && y <= byte.MaxValue)
|
||||
if (x is >= byte.MinValue and <= byte.MaxValue && y is >= byte.MinValue and <= byte.MaxValue)
|
||||
{
|
||||
flags |= SaveFlag.LocationByteXY;
|
||||
}
|
||||
|
|
@ -1285,7 +1291,7 @@ namespace Server
|
|||
var worldLoc = GetWorldLocation();
|
||||
var update = (flags & ItemDelta.Update) != 0;
|
||||
|
||||
if (update && m_Parent is Container contParent && !contParent.IsPublicContainer)
|
||||
if (update && m_Parent is Container { IsPublicContainer: false } contParent)
|
||||
{
|
||||
var rootParent = contParent.RootParent as Mobile;
|
||||
Mobile tradeRecip = null;
|
||||
|
|
@ -1332,39 +1338,36 @@ namespace Server
|
|||
|
||||
if (openers != null)
|
||||
{
|
||||
lock (openers)
|
||||
for (var i = 0; i < openers.Count; ++i)
|
||||
{
|
||||
for (var i = 0; i < openers.Count; ++i)
|
||||
var mob = openers[i];
|
||||
|
||||
var range = GetUpdateRange(mob);
|
||||
|
||||
if (mob.Map != map || !mob.InRange(worldLoc, range))
|
||||
{
|
||||
var mob = openers[i];
|
||||
|
||||
var range = GetUpdateRange(mob);
|
||||
|
||||
if (mob.Map != map || !mob.InRange(worldLoc, range))
|
||||
openers.RemoveAt(i--);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mob == rootParent || mob == tradeRecip)
|
||||
{
|
||||
openers.RemoveAt(i--);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
var ns = mob.NetState;
|
||||
|
||||
if (ns != null && mob.CanSee(this))
|
||||
{
|
||||
if (mob == rootParent || mob == tradeRecip)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var ns = mob.NetState;
|
||||
|
||||
if (ns != null && mob.CanSee(this))
|
||||
{
|
||||
ns.SendContainerContentUpdate(this);
|
||||
SendOPLPacketTo(ns);
|
||||
}
|
||||
ns.SendContainerContentUpdate(this);
|
||||
SendOPLPacketTo(ns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (openers.Count == 0)
|
||||
{
|
||||
contParent.Openers = null;
|
||||
}
|
||||
if (openers.Count == 0)
|
||||
{
|
||||
contParent.Openers = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1719,6 +1722,7 @@ namespace Server
|
|||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool GetFlag(ImplFlag flag) => (m_Flags & flag) != 0;
|
||||
|
||||
public BounceInfo GetBounce() => LookupCompactInfo()?.m_Bounce;
|
||||
|
|
@ -1748,12 +1752,9 @@ namespace Server
|
|||
parentItem.OnItemBounceCleared(this);
|
||||
}
|
||||
}
|
||||
else if (bounce.Parent is Mobile parentMobile)
|
||||
else if (bounce.Parent is Mobile { Deleted: false } parentMobile)
|
||||
{
|
||||
if (!parentMobile.Deleted)
|
||||
{
|
||||
parentMobile.OnItemBounceCleared(this);
|
||||
}
|
||||
parentMobile.OnItemBounceCleared(this);
|
||||
}
|
||||
|
||||
VerifyCompactInfo();
|
||||
|
|
@ -2344,7 +2345,7 @@ namespace Server
|
|||
var itemID = m_ItemID;
|
||||
var doubled = m_Amount > 1;
|
||||
|
||||
if (itemID >= 0xEEA && itemID <= 0xEF2) // Are we coins?
|
||||
if (itemID is >= 0xEEA and <= 0xEF2) // Are we coins?
|
||||
{
|
||||
var coinBase = (itemID - 0xEEA) / 3;
|
||||
coinBase *= 3;
|
||||
|
|
@ -2408,7 +2409,7 @@ namespace Server
|
|||
}
|
||||
|
||||
#nullable enable
|
||||
public void InvalidateProperties()
|
||||
public virtual void InvalidateProperties()
|
||||
{
|
||||
if (!ObjectPropertyList.Enabled)
|
||||
{
|
||||
|
|
@ -2573,6 +2574,10 @@ namespace Server
|
|||
}
|
||||
}
|
||||
|
||||
public virtual void BeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Deserialize(IGenericReader reader)
|
||||
{
|
||||
var version = reader.ReadInt();
|
||||
|
|
@ -3251,10 +3256,8 @@ namespace Server
|
|||
m_DeltaFlags &= ~flags;
|
||||
}
|
||||
|
||||
public static int ProcessDeltaQueue()
|
||||
public static void ProcessDeltaQueue()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
var limit = m_DeltaQueue.Count;
|
||||
|
||||
while (m_DeltaQueue.Count > 0 && --limit >= 0)
|
||||
|
|
@ -3266,8 +3269,6 @@ namespace Server
|
|||
continue;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
item.SetFlag(ImplFlag.InQueue, false);
|
||||
|
||||
try
|
||||
|
|
@ -3288,8 +3289,6 @@ namespace Server
|
|||
Console.WriteLine("Warning: {0} items left in delta queue after processing.", m_DeltaQueue.Count);
|
||||
Utility.PopColor();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public virtual void OnDelete()
|
||||
|
|
|
|||
37
Projects/Server/Json/Converters/ClientVersionConverter.cs
Normal file
37
Projects/Server/Json/Converters/ClientVersionConverter.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ClientVersionConverter.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Server.Json
|
||||
{
|
||||
public class ClientVersionConverter : JsonConverter<ClientVersion>
|
||||
{
|
||||
public override ClientVersion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
return new ClientVersion(reader.GetString());
|
||||
}
|
||||
|
||||
throw new JsonException($"Value must be a string");
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, ClientVersion value, JsonSerializerOptions options) =>
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ClientVersionConverterFactory.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Server.Json
|
||||
{
|
||||
public class ClientVersionConverterFactory : JsonConverterFactory
|
||||
{
|
||||
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(ClientVersion);
|
||||
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) =>
|
||||
new ClientVersionConverter();
|
||||
}
|
||||
}
|
||||
149
Projects/Server/Json/Converters/FlagsConverter.cs
Normal file
149
Projects/Server/Json/Converters/FlagsConverter.cs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: FlagsConverter.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Server.Json
|
||||
{
|
||||
public class FlagsConverter<T> : JsonConverter<T> where T : struct, Enum
|
||||
{
|
||||
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var flags = 0ul;
|
||||
var underlyingType = Enum.GetUnderlyingType(typeof(T));
|
||||
|
||||
while (true)
|
||||
{
|
||||
reader.Read();
|
||||
if (reader.TokenType == JsonTokenType.EndObject)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (reader.TokenType != JsonTokenType.PropertyName)
|
||||
{
|
||||
throw new JsonException("Invalid Json structure for Flag object");
|
||||
}
|
||||
|
||||
var key = reader.GetString();
|
||||
|
||||
reader.Read();
|
||||
|
||||
if (!reader.GetBoolean() || !Enum.TryParse<T>(key, out var val))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
flags |= ConvertToUInt64(underlyingType, val);
|
||||
}
|
||||
|
||||
switch (Type.GetTypeCode(underlyingType))
|
||||
{
|
||||
case TypeCode.SByte:
|
||||
{
|
||||
var num = (sbyte)flags;
|
||||
return Unsafe.As<sbyte, T>(ref num);
|
||||
}
|
||||
case TypeCode.Byte:
|
||||
{
|
||||
var num = (byte)flags;
|
||||
return Unsafe.As<byte, T>(ref num);
|
||||
}
|
||||
case TypeCode.Int16:
|
||||
{
|
||||
var num = (short)flags;
|
||||
return Unsafe.As<short, T>(ref num);
|
||||
}
|
||||
case TypeCode.UInt16:
|
||||
{
|
||||
var num = (ushort)flags;
|
||||
return Unsafe.As<ushort, T>(ref num);
|
||||
}
|
||||
case TypeCode.UInt32:
|
||||
{
|
||||
var num = (uint)flags;
|
||||
return Unsafe.As<uint, T>(ref num);
|
||||
}
|
||||
case TypeCode.Int64:
|
||||
{
|
||||
var num = (long)flags;
|
||||
return Unsafe.As<long, T>(ref num);
|
||||
}
|
||||
case TypeCode.UInt64:
|
||||
{
|
||||
return Unsafe.As<ulong, T>(ref flags);
|
||||
}
|
||||
default:
|
||||
{
|
||||
var num = (int)flags;
|
||||
return Unsafe.As<int, T>(ref num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
var underlyingType = Enum.GetUnderlyingType(typeof(T));
|
||||
var size = GetUnderlyingTypeLength(Type.GetTypeCode(underlyingType)) - 1;
|
||||
var intValue = ConvertToUInt64(underlyingType, value);
|
||||
|
||||
foreach (var flagName in Enum.GetNames(typeof(T)))
|
||||
{
|
||||
var flagValue = Enum.Parse<T>(flagName, false);
|
||||
var flag = ConvertToUInt64(underlyingType, flagValue);
|
||||
if (flag == 0 || (flag & size) == 0)
|
||||
{
|
||||
writer.WriteBoolean(flagName, (intValue & flag) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static ulong ConvertToUInt64(Type underlyingType, object value) =>
|
||||
Type.GetTypeCode(underlyingType) switch
|
||||
{
|
||||
TypeCode.SByte => (ulong)(sbyte)value,
|
||||
TypeCode.Byte => (byte)value,
|
||||
TypeCode.Int16 => (ulong)(short)value,
|
||||
TypeCode.UInt16 => (ushort)value,
|
||||
TypeCode.Int32 => (ulong)(int)value,
|
||||
TypeCode.UInt32 => (uint)value,
|
||||
TypeCode.Int64 => (ulong)(long)value,
|
||||
TypeCode.UInt64 => (ulong)value,
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong GetUnderlyingTypeLength(TypeCode typeCode) =>
|
||||
typeCode switch
|
||||
{
|
||||
TypeCode.Byte => 8,
|
||||
TypeCode.SByte => 8,
|
||||
TypeCode.Int16 => 16,
|
||||
TypeCode.UInt16 => 16,
|
||||
TypeCode.Char => 16,
|
||||
TypeCode.Int32 => 32,
|
||||
TypeCode.UInt32 => 32,
|
||||
TypeCode.Int64 => 64,
|
||||
TypeCode.UInt64 => 64,
|
||||
_ => 64
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ namespace Server.Json
|
|||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
options.Converters.Add(new ClientVersionConverterFactory());
|
||||
options.Converters.Add(new GuidConverterFactory());
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
options.Converters.Add(new MapConverterFactory());
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Runtime.InteropServices;
|
||||
|
|
@ -55,7 +56,8 @@ namespace Server
|
|||
public static readonly bool IsDarwin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
public static readonly bool IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
|
||||
public static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || IsFreeBSD;
|
||||
public static readonly bool Unix = IsDarwin || IsFreeBSD || IsLinux;
|
||||
public static readonly bool IsBSD = IsDarwin || IsFreeBSD;
|
||||
public static readonly bool Unix = IsBSD || IsLinux;
|
||||
|
||||
private const string AssembliesConfiguration = "Data/assemblies.json";
|
||||
|
||||
|
|
@ -155,6 +157,11 @@ namespace Server
|
|||
set => _now = value;
|
||||
}
|
||||
|
||||
private static long _cycleIndex = 1;
|
||||
private static float[] _cyclesPerSecond = new float[100];
|
||||
|
||||
public static float CyclesPerSecond => _cyclesPerSecond[(_cycleIndex - 1) % _cyclesPerSecond.Length];
|
||||
|
||||
public static bool MultiProcessor { get; private set; }
|
||||
|
||||
public static int ProcessorCount { get; private set; }
|
||||
|
|
@ -242,6 +249,17 @@ namespace Server
|
|||
{
|
||||
fullPath = Path.Combine(p, path);
|
||||
|
||||
if (IsLinux)
|
||||
{
|
||||
var fi = new FileInfo(fullPath);
|
||||
fullPath = fi.Directory!.EnumerateFiles(
|
||||
fi.Name,
|
||||
new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }
|
||||
).FirstOrDefault()?.FullName;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
break;
|
||||
|
|
@ -488,40 +506,51 @@ namespace Server
|
|||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
const bool idleCPU = true;
|
||||
#else
|
||||
var idleCPU = ServerConfiguration.GetOrUpdateSetting("core.enableIdleCPU", false);
|
||||
#endif
|
||||
|
||||
long last = TickCount;
|
||||
const int interval = 100;
|
||||
int idleCount = 0;
|
||||
const float ticksPerSecond = 1000 * interval;
|
||||
|
||||
int sample = 0;
|
||||
|
||||
while (!Closing)
|
||||
{
|
||||
_tickCount = TickCount;
|
||||
_now = DateTime.UtcNow;
|
||||
|
||||
var events = Mobile.ProcessDeltaQueue();
|
||||
events += Item.ProcessDeltaQueue();
|
||||
events += Timer.Slice(_tickCount);
|
||||
Mobile.ProcessDeltaQueue();
|
||||
Item.ProcessDeltaQueue();
|
||||
Timer.Slice(_tickCount);
|
||||
|
||||
// Handle networking
|
||||
events += TcpServer.Slice();
|
||||
events += NetState.HandleAllReceives();
|
||||
events += NetState.Slice();
|
||||
TcpServer.Slice();
|
||||
NetState.Slice();
|
||||
|
||||
// Execute captured post-await methods (like Timer.Pause)
|
||||
events += LoopContext.ExecuteTasks();
|
||||
LoopContext.ExecuteTasks();
|
||||
|
||||
Timer.CheckTimerPool(); // Check for pool depletion so we can async refill it.
|
||||
|
||||
_tickCount = 0;
|
||||
_now = DateTime.MinValue;
|
||||
|
||||
if (events > 0)
|
||||
if (idleCPU && ++sample % interval == 0)
|
||||
{
|
||||
idleCount = 0;
|
||||
continue;
|
||||
}
|
||||
var now = TickCount;
|
||||
|
||||
if (++idleCount > interval)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
var cyclesPerSecond = ticksPerSecond / (now - last);
|
||||
_cyclesPerSecond[_cycleIndex++ % _cyclesPerSecond.Length] = cyclesPerSecond;
|
||||
last = now;
|
||||
|
||||
if (cyclesPerSecond > 80)
|
||||
{
|
||||
Thread.Sleep(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -692,7 +692,7 @@ namespace Server
|
|||
var tile = staticTiles[i];
|
||||
var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
|
||||
|
||||
if (id.Surface || (id.Flags & TileFlag.Wet) != 0)
|
||||
if (id.Surface || id.Wet)
|
||||
{
|
||||
var tileZ = tile.Z + id.CalcHeight;
|
||||
|
||||
|
|
@ -720,7 +720,7 @@ namespace Server
|
|||
{
|
||||
var id = item.ItemData;
|
||||
|
||||
if (id.Surface || (id.Flags & TileFlag.Wet) != 0)
|
||||
if (id.Surface || id.Wet)
|
||||
{
|
||||
var itemZ = item.Z + id.CalcHeight;
|
||||
|
||||
|
|
|
|||
|
|
@ -916,8 +916,15 @@ namespace Server
|
|||
else
|
||||
{
|
||||
m_NetState.SendChangeCombatant(m_Combatant.Serial);
|
||||
Timer.StartTimer(ExpireCombatantDelay, ExpireCombatant, out _expireCombatantTimerToken);
|
||||
Timer.StartTimer(TimeSpan.FromSeconds(0.01), 0, CheckCombatTime, out _combatTimerToken);
|
||||
if (!_expireCombatantTimerToken.Running)
|
||||
{
|
||||
Timer.StartTimer(ExpireCombatantDelay, ExpireCombatant, out _expireCombatantTimerToken);
|
||||
}
|
||||
|
||||
if (!_combatTimerToken.Running)
|
||||
{
|
||||
Timer.StartTimer(TimeSpan.FromSeconds(0.01), 0, CheckCombatTime, out _combatTimerToken);
|
||||
}
|
||||
|
||||
if (CanBeHarmful(m_Combatant, false))
|
||||
{
|
||||
|
|
@ -1147,9 +1154,6 @@ namespace Server
|
|||
|
||||
public virtual bool ShouldCheckStatTimers => true;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public DateTime CreationTime { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int LightLevel
|
||||
{
|
||||
|
|
@ -2472,6 +2476,12 @@ namespace Server
|
|||
AddNameProperties(list);
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, readOnly: true)]
|
||||
public DateTime Created { get; set; } = Core.Now;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
DateTime ISerializable.LastSerialized { get; set; } = Core.Now;
|
||||
|
||||
long ISerializable.SavePosition { get; set; } = -1;
|
||||
|
||||
BufferWriter ISerializable.SaveBuffer { get; set; }
|
||||
|
|
@ -2483,7 +2493,7 @@ namespace Server
|
|||
|
||||
public virtual void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.Write(32); // version
|
||||
writer.Write(33); // version
|
||||
|
||||
writer.WriteDeltaTime(LastStrGain);
|
||||
writer.WriteDeltaTime(LastIntGain);
|
||||
|
|
@ -2519,7 +2529,7 @@ namespace Server
|
|||
|
||||
writer.Write(Corpse);
|
||||
|
||||
writer.Write(CreationTime);
|
||||
// writer.Write(CreationTime);
|
||||
|
||||
Stabled.Tidy();
|
||||
writer.Write(Stabled);
|
||||
|
|
@ -6167,12 +6177,21 @@ namespace Server
|
|||
{
|
||||
}
|
||||
|
||||
public virtual void BeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Deserialize(IGenericReader reader)
|
||||
{
|
||||
var version = reader.ReadInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 33:
|
||||
{
|
||||
// Removed created
|
||||
goto case 32;
|
||||
}
|
||||
case 32:
|
||||
{
|
||||
// Removed StuckMenu
|
||||
|
|
@ -6232,7 +6251,10 @@ namespace Server
|
|||
}
|
||||
case 23:
|
||||
{
|
||||
CreationTime = reader.ReadDateTime();
|
||||
if (version < 33)
|
||||
{
|
||||
Created = reader.ReadDateTime();
|
||||
}
|
||||
|
||||
goto case 22;
|
||||
}
|
||||
|
|
@ -7818,7 +7840,6 @@ namespace Server
|
|||
DamageEntries = new List<DamageEntry>();
|
||||
|
||||
NextSkillTime = Core.TickCount;
|
||||
CreationTime = Core.Now;
|
||||
}
|
||||
|
||||
public virtual void Delta(MobileDelta flag)
|
||||
|
|
@ -7848,10 +7869,8 @@ namespace Server
|
|||
}
|
||||
}
|
||||
|
||||
public static int ProcessDeltaQueue()
|
||||
public static void ProcessDeltaQueue()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
var limit = m_DeltaQueue.Count;
|
||||
|
||||
while (m_DeltaQueue.Count > 0 && --limit >= 0)
|
||||
|
|
@ -7863,8 +7882,6 @@ namespace Server
|
|||
continue;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
mob.m_InDeltaQueue = false;
|
||||
|
||||
try
|
||||
|
|
@ -7885,8 +7902,6 @@ namespace Server
|
|||
Console.WriteLine("Warning: {0} mobiles left in delta queue after processing.", m_DeltaQueue.Count);
|
||||
Utility.PopColor();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public virtual void OnKillsChange(int oldValue)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ namespace Server.Network
|
|||
{
|
||||
public interface ISocket
|
||||
{
|
||||
public IntPtr Handle { get; }
|
||||
|
||||
public EndPoint LocalEndPoint { get; }
|
||||
|
||||
public EndPoint RemoteEndPoint { get; }
|
||||
|
|
@ -33,6 +35,8 @@ namespace Server.Network
|
|||
|
||||
public Task<int> ReceiveAsync(IList<ArraySegment<byte>> buffer, SocketFlags flags);
|
||||
|
||||
public int Receive(IList<ArraySegment<byte>> buffers, SocketFlags flags);
|
||||
|
||||
public void Shutdown(SocketShutdown how);
|
||||
|
||||
public void Close();
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace Server.Network
|
|||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
{
|
||||
file.WriteLine($"{ns}, {ns._recvState}, {ns._sendState}, {ns._protocolState}, {ns._parserState}");
|
||||
file.WriteLine($"{ns}, {ns._protocolState}, {ns._parserState}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
337
Projects/Server/Network/NetState/NetState.cs
Normal file → Executable file
337
Projects/Server/Network/NetState/NetState.cs
Normal file → Executable file
|
|
@ -20,8 +20,9 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Network;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
using Server.Accounting;
|
||||
using Server.Diagnostics;
|
||||
using Server.Gumps;
|
||||
|
|
@ -43,12 +44,15 @@ namespace Server.Network
|
|||
|
||||
private const int RecvPipeSize = 1024 * 64;
|
||||
private const int SendPipeSize = 1024 * 256;
|
||||
private static int GumpCap = 512;
|
||||
private static int HuePickerCap = 512;
|
||||
private static int MenuCap = 512;
|
||||
private static int PacketPerSecondThreshold = 3000;
|
||||
private const int GumpCap = 512;
|
||||
private const int HuePickerCap = 512;
|
||||
private const int MenuCap = 512;
|
||||
private const int PacketPerSecondThreshold = 3000;
|
||||
|
||||
private static GCHandle[] _polledStates = new GCHandle[2048];
|
||||
private static readonly IPollGroup _pollGroup = PollGroup.Create();
|
||||
private static readonly Queue<NetState> FlushPending = new(2048);
|
||||
private static readonly Queue<NetState> FlushedPartials = new(2048);
|
||||
private static readonly ConcurrentQueue<NetState> Disposed = new();
|
||||
|
||||
public static NetStateCreatedCallback CreatedCallback { get; set; }
|
||||
|
|
@ -56,7 +60,7 @@ namespace Server.Network
|
|||
private readonly string _toString;
|
||||
private ClientVersion _version;
|
||||
private long _nextActivityCheck;
|
||||
private int _running;
|
||||
private bool _running = true;
|
||||
private volatile DecodePacket _packetDecoder;
|
||||
private volatile EncodePacket _packetEncoder;
|
||||
private bool _flushQueued;
|
||||
|
|
@ -66,14 +70,12 @@ namespace Server.Network
|
|||
|
||||
internal int _authId;
|
||||
internal int _seed;
|
||||
internal ParserState _parserState = ParserState.Uninitialized;
|
||||
internal ProtocolState _protocolState = ProtocolState.Uninitialized;
|
||||
internal RecvState _recvState = RecvState.Uninitialized;
|
||||
internal SendState _sendState = SendState.Uninitialized;
|
||||
internal ParserState _parserState = ParserState.AwaitingNextPacket;
|
||||
internal ProtocolState _protocolState = ProtocolState.AwaitingSeed;
|
||||
internal GCHandle _handle;
|
||||
|
||||
internal enum ParserState
|
||||
{
|
||||
Uninitialized,
|
||||
AwaitingNextPacket,
|
||||
AwaitingPartialPacket,
|
||||
ProcessingPacket,
|
||||
|
|
@ -83,7 +85,6 @@ namespace Server.Network
|
|||
|
||||
internal enum ProtocolState
|
||||
{
|
||||
Uninitialized,
|
||||
AwaitingSeed, // Based on the way the seed arrives, we know if this is a login server or a game server connection
|
||||
|
||||
LoginServer_AwaitingLogin,
|
||||
|
|
@ -96,38 +97,12 @@ namespace Server.Network
|
|||
Error
|
||||
}
|
||||
|
||||
internal enum RecvState
|
||||
{
|
||||
Uninitialized,
|
||||
AwaitingMemory,
|
||||
AwaitingRecv,
|
||||
DataReceived,
|
||||
Exited,
|
||||
}
|
||||
|
||||
internal enum SendState
|
||||
{
|
||||
Uninitialized,
|
||||
AwaitingData,
|
||||
Sending,
|
||||
SendCompleted,
|
||||
Exited,
|
||||
}
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
GumpCap = ServerConfiguration.GetOrUpdateSetting("netstate.gumpCap", GumpCap);
|
||||
HuePickerCap = ServerConfiguration.GetOrUpdateSetting("netstate.huePickerCap", HuePickerCap);
|
||||
MenuCap = ServerConfiguration.GetOrUpdateSetting("netstate.menuCap", MenuCap);
|
||||
PacketPerSecondThreshold = ServerConfiguration.GetOrUpdateSetting("netstate.packetsPerSecondThreshold", PacketPerSecondThreshold);
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
Timer.DelayCall(TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1.5), CheckAllAlive);
|
||||
}
|
||||
|
||||
public NetState(ISocket connection)
|
||||
public NetState(Socket connection)
|
||||
{
|
||||
Connection = connection;
|
||||
Seeded = false;
|
||||
|
|
@ -138,6 +113,7 @@ namespace Server.Network
|
|||
RecvPipe = new Pipe<byte>(GC.AllocateUninitializedArray<byte>(RecvPipeSize));
|
||||
SendPipe = new Pipe<byte>(GC.AllocateUninitializedArray<byte>(SendPipeSize));
|
||||
_nextActivityCheck = Core.TickCount + 30000;
|
||||
ConnectedOn = Core.Now;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -151,7 +127,17 @@ namespace Server.Network
|
|||
_toString = "(error)";
|
||||
}
|
||||
|
||||
ConnectedOn = Core.Now;
|
||||
_handle = GCHandle.Alloc(this);
|
||||
|
||||
try
|
||||
{
|
||||
_pollGroup.Add(connection, _handle);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TraceException(ex);
|
||||
Disconnect("Unable to add socket to poll group");
|
||||
}
|
||||
|
||||
CreatedCallback?.Invoke(this);
|
||||
}
|
||||
|
|
@ -174,25 +160,21 @@ namespace Server.Network
|
|||
set => _packetEncoder = value;
|
||||
}
|
||||
|
||||
public int CurrentPacket { get; internal set; }
|
||||
|
||||
public bool SentFirstPacket { get; set; }
|
||||
|
||||
public bool BlockAllPackets { get; set; }
|
||||
|
||||
public List<SecureTrade> Trades { get; }
|
||||
|
||||
public bool Running
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _running == 1;
|
||||
}
|
||||
|
||||
public bool Seeded { get; set; }
|
||||
|
||||
public Pipe<byte> RecvPipe { get; }
|
||||
|
||||
public Pipe<byte> SendPipe { get; }
|
||||
|
||||
public ISocket Connection { get; }
|
||||
public Socket Connection { get; }
|
||||
|
||||
public bool CompressionEnabled { get; set; }
|
||||
|
||||
|
|
@ -524,36 +506,21 @@ namespace Server.Network
|
|||
}
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
public void HandleReceive()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _running, 1, 0) == 1 || Connection == null)
|
||||
if (!_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_parserState = ParserState.AwaitingNextPacket;
|
||||
_protocolState = ProtocolState.AwaitingSeed;
|
||||
|
||||
ThreadPool.UnsafeQueueUserWorkItem(RecvTask, null);
|
||||
ThreadPool.UnsafeQueueUserWorkItem(SendTask, null);
|
||||
}
|
||||
|
||||
// Return true if there was any data to be processed. False otherwise. Used for idle detection.
|
||||
public bool HandleReceive()
|
||||
{
|
||||
if (!Running)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool active = false;
|
||||
ReceiveData();
|
||||
|
||||
var reader = RecvPipe.Reader;
|
||||
|
||||
try
|
||||
{
|
||||
// Process as many packets as we can synchronously
|
||||
while (Running && _parserState != ParserState.Error && _protocolState != ProtocolState.Error)
|
||||
while (_running && _parserState != ParserState.Error && _protocolState != ProtocolState.Error)
|
||||
{
|
||||
var result = reader.TryRead();
|
||||
var length = result.Length;
|
||||
|
|
@ -563,9 +530,6 @@ namespace Server.Network
|
|||
break;
|
||||
}
|
||||
|
||||
// There was at least some data found, so it's not idle.
|
||||
active = true;
|
||||
|
||||
var packetReader = new CircularBufferReader(result.Buffer);
|
||||
var packetId = packetReader.ReadByte();
|
||||
int packetLength = length;
|
||||
|
|
@ -580,12 +544,6 @@ namespace Server.Network
|
|||
{
|
||||
switch (_protocolState)
|
||||
{
|
||||
case ProtocolState.Uninitialized:
|
||||
{
|
||||
HandleError(packetId, packetLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
case ProtocolState.AwaitingSeed:
|
||||
{
|
||||
if (packetId == 0xEF)
|
||||
|
|
@ -604,7 +562,7 @@ namespace Server.Network
|
|||
if (seed == 0)
|
||||
{
|
||||
HandleError(0, 0);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
_seed = seed;
|
||||
|
|
@ -626,7 +584,7 @@ namespace Server.Network
|
|||
{
|
||||
LogInfo("Possible encrypted client detected, disconnecting...");
|
||||
HandleError(packetId, packetLength);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
_parserState = ParserState.ProcessingPacket;
|
||||
|
|
@ -643,7 +601,7 @@ namespace Server.Network
|
|||
if (packetId != 0xA0)
|
||||
{
|
||||
HandleError(packetId, packetLength);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
_parserState = ParserState.ProcessingPacket;
|
||||
|
|
@ -665,7 +623,7 @@ namespace Server.Network
|
|||
_parserState = ParserState.AwaitingNextPacket;
|
||||
_protocolState = ProtocolState.AwaitingSeed;
|
||||
#endif
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
case ProtocolState.GameServer_AwaitingGameServerLogin:
|
||||
|
|
@ -673,7 +631,7 @@ namespace Server.Network
|
|||
if (packetId != 0x91 && packetId != 0x80)
|
||||
{
|
||||
HandleError(packetId, packetLength);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
_parserState = ParserState.ProcessingPacket;
|
||||
|
|
@ -719,8 +677,6 @@ namespace Server.Network
|
|||
TraceException(ex);
|
||||
Disconnect("Exception during HandleReceive");
|
||||
}
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
|
@ -811,83 +767,56 @@ namespace Server.Network
|
|||
return ParserState.AwaitingNextPacket;
|
||||
}
|
||||
|
||||
private async void SendTask(object state)
|
||||
private bool Flush()
|
||||
{
|
||||
_flushQueued = false;
|
||||
|
||||
if (Connection == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SendPipe.Writer.Flush();
|
||||
|
||||
var reader = SendPipe.Reader;
|
||||
var result = reader.TryRead();
|
||||
|
||||
if (result.IsClosed || result.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var bytesWritten = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (Running)
|
||||
{
|
||||
_sendState = SendState.AwaitingData;
|
||||
|
||||
var result = await reader.Read();
|
||||
|
||||
if (result.IsClosed || !Running)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.Length <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_sendState = SendState.Sending;
|
||||
|
||||
var bytesWritten = await Connection.SendAsync(result.Buffer, SocketFlags.None);
|
||||
|
||||
_sendState = SendState.SendCompleted;
|
||||
|
||||
if (bytesWritten > 0)
|
||||
{
|
||||
_nextActivityCheck = Core.TickCount + 90000;
|
||||
reader.Advance((uint)bytesWritten);
|
||||
}
|
||||
}
|
||||
|
||||
// Grab any remaining data and flush it
|
||||
var data = reader.TryRead();
|
||||
|
||||
if (data.Length > 0)
|
||||
{
|
||||
_sendState = SendState.Sending;
|
||||
Connection.Send(data.Buffer, SocketFlags.None);
|
||||
|
||||
reader.Advance((uint)data.Length);
|
||||
}
|
||||
bytesWritten = Connection.Send(result.Buffer, SocketFlags.None);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
// If the user closes the connection (or the recv side does)
|
||||
// between the check for m_Running above and the call to SendAsync,
|
||||
// we can still get a socket exception here. That's ok.
|
||||
// Socket exceptions are generally ok, just spammy
|
||||
#if DEBUG
|
||||
Console.WriteLine(ex);
|
||||
#endif
|
||||
|
||||
Disconnect(string.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(ex);
|
||||
#endif
|
||||
Disconnect($"Disconnected with error: {ex}");
|
||||
TraceException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
Connection.Shutdown(SocketShutdown.Both);
|
||||
Connection.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TraceException(ex);
|
||||
}
|
||||
|
||||
Disconnect("Exiting SendTask.");
|
||||
_sendState = SendState.Exited;
|
||||
if (bytesWritten > 0)
|
||||
{
|
||||
_nextActivityCheck = Core.TickCount + 90000;
|
||||
reader.Advance((uint)bytesWritten);
|
||||
}
|
||||
|
||||
return bytesWritten == result.Length;
|
||||
}
|
||||
|
||||
private void DecodePacket(ArraySegment<byte>[] buffer, ref int length)
|
||||
|
|
@ -896,47 +825,21 @@ namespace Server.Network
|
|||
_packetDecoder?.Invoke(cBuffer, ref length);
|
||||
}
|
||||
|
||||
private async void RecvTask(object state)
|
||||
private void ReceiveData()
|
||||
{
|
||||
var socket = Connection;
|
||||
var writer = RecvPipe.Writer;
|
||||
var result = writer.TryGetMemory();
|
||||
|
||||
if (result.IsClosed || result.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bytesWritten = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (Running)
|
||||
{
|
||||
_recvState = RecvState.AwaitingMemory;
|
||||
var result = await writer.GetMemory();
|
||||
|
||||
if (result.IsClosed || !Running)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.Length <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_recvState = RecvState.AwaitingRecv;
|
||||
|
||||
var bytesWritten = await socket.ReceiveAsync(result.Buffer, SocketFlags.None);
|
||||
if (bytesWritten <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_recvState = RecvState.DataReceived;
|
||||
|
||||
DecodePacket(result.Buffer, ref bytesWritten);
|
||||
|
||||
writer.Advance((uint)bytesWritten);
|
||||
_nextActivityCheck = Core.TickCount + 90000;
|
||||
|
||||
// No need to flush
|
||||
}
|
||||
|
||||
Disconnect(string.Empty);
|
||||
bytesWritten = Connection.Receive(result.Buffer, SocketFlags.None);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
|
|
@ -953,38 +856,20 @@ namespace Server.Network
|
|||
#if DEBUG
|
||||
Console.WriteLine(ex);
|
||||
#endif
|
||||
Disconnect("RecvTask exited unexpectedly.");
|
||||
Disconnect($"Disconnected with error: {ex}");
|
||||
TraceException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_recvState = RecvState.Exited;
|
||||
}
|
||||
}
|
||||
|
||||
public static int HandleAllReceives()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
if (bytesWritten <= 0)
|
||||
{
|
||||
if (ns.HandleReceive())
|
||||
{
|
||||
count++;
|
||||
}
|
||||
Disconnect(string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
DecodePacket(result.Buffer, ref bytesWritten);
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
if (Connection != null)
|
||||
{
|
||||
SendPipe.Writer.Flush();
|
||||
}
|
||||
|
||||
_flushQueued = false;
|
||||
writer.Advance((uint)bytesWritten);
|
||||
_nextActivityCheck = Core.TickCount + 90000;
|
||||
}
|
||||
|
||||
public static void FlushAll()
|
||||
|
|
@ -995,22 +880,38 @@ namespace Server.Network
|
|||
}
|
||||
}
|
||||
|
||||
public static int Slice()
|
||||
public static void Slice()
|
||||
{
|
||||
int count = 0;
|
||||
while (FlushPending.Count != 0)
|
||||
int count = _pollGroup.Poll(ref _polledStates);
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
FlushPending.Dequeue()?.Flush();
|
||||
count++;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
(_polledStates[i].Target as NetState)?.HandleReceive();
|
||||
_polledStates[i] = default;
|
||||
}
|
||||
}
|
||||
|
||||
while (FlushPending.TryDequeue(out var ns))
|
||||
{
|
||||
if (!ns.Flush())
|
||||
{
|
||||
// Incomplete data, so we need to requeue
|
||||
FlushedPartials.Enqueue(ns);
|
||||
}
|
||||
}
|
||||
|
||||
var hasDisposes = !Disposed.IsEmpty;
|
||||
while (Disposed.TryDequeue(out var ns))
|
||||
{
|
||||
TcpServer.Instances.Remove(ns);
|
||||
ns.Dispose();
|
||||
}
|
||||
|
||||
return count;
|
||||
if (hasDisposes)
|
||||
{
|
||||
_pollGroup.Poll(ref _polledStates);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckAlive(long curTicks)
|
||||
|
|
@ -1064,11 +965,13 @@ namespace Server.Network
|
|||
|
||||
public void Disconnect(string reason)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _running, 0, 1) == 0)
|
||||
if (!_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_running = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (_disconnectReason != string.Empty)
|
||||
|
|
@ -1082,7 +985,6 @@ namespace Server.Network
|
|||
}
|
||||
|
||||
_disconnectReason = reason;
|
||||
|
||||
Disposed.Enqueue(this);
|
||||
}
|
||||
|
||||
|
|
@ -1114,6 +1016,11 @@ namespace Server.Network
|
|||
{
|
||||
TraceDisconnect(_disconnectReason, _toString);
|
||||
|
||||
if (_running)
|
||||
{
|
||||
throw new Exception("Disconnected a NetState that is still running.");
|
||||
}
|
||||
|
||||
#if THREADGUARD
|
||||
if (Thread.CurrentThread != Core.Thread)
|
||||
{
|
||||
|
|
@ -1125,8 +1032,18 @@ namespace Server.Network
|
|||
}
|
||||
#endif
|
||||
|
||||
RecvPipe.Writer.Close();
|
||||
SendPipe.Writer.Close();
|
||||
TcpServer.Instances.Remove(this);
|
||||
try
|
||||
{
|
||||
_pollGroup.Remove(Connection);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TraceException(ex);
|
||||
}
|
||||
|
||||
Connection.Close();
|
||||
_handle.Free();
|
||||
|
||||
var m = Mobile;
|
||||
Mobile = null;
|
||||
|
|
|
|||
|
|
@ -125,7 +125,6 @@ namespace Server.Network
|
|||
return outputIdx;
|
||||
}
|
||||
|
||||
|
||||
public static int Compress(CircularBuffer<byte> input, CircularBuffer<byte> output)
|
||||
{
|
||||
if (input.Length > DefiniteOverflow)
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: NetworkSocket.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Network
|
||||
{
|
||||
public class NetworkSocket : ISocket
|
||||
{
|
||||
private readonly Socket _connection;
|
||||
|
||||
public Socket Connection
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _connection;
|
||||
}
|
||||
|
||||
public EndPoint LocalEndPoint
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _connection.LocalEndPoint;
|
||||
}
|
||||
|
||||
public EndPoint RemoteEndPoint
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _connection.RemoteEndPoint;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public NetworkSocket(Socket connection) => _connection = connection;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Task<int> SendAsync(IList<ArraySegment<byte>> buffers, SocketFlags flags) =>
|
||||
_connection.SendAsync(buffers, flags);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Send(IList<ArraySegment<byte>> buffers, SocketFlags flags) => _connection.Send(buffers, flags);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Task<int> ReceiveAsync(IList<ArraySegment<byte>> buffers, SocketFlags flags) =>
|
||||
_connection.ReceiveAsync(buffers, flags);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Shutdown(SocketShutdown how) => _connection.Shutdown(how);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Close() => _connection.Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -73,40 +73,20 @@ namespace Server.Network
|
|||
|
||||
int genderRace = reader.ReadByte();
|
||||
|
||||
int str = reader.ReadByte();
|
||||
int dex = reader.ReadByte();
|
||||
int intl = reader.ReadByte();
|
||||
int is1 = reader.ReadByte();
|
||||
int vs1 = reader.ReadByte();
|
||||
int is2 = reader.ReadByte();
|
||||
int vs2 = reader.ReadByte();
|
||||
int is3 = reader.ReadByte();
|
||||
int vs3 = reader.ReadByte();
|
||||
var stats = new StatNameValue[]
|
||||
{
|
||||
new(StatType.Str, reader.ReadByte()),
|
||||
new(StatType.Dex, reader.ReadByte()),
|
||||
new(StatType.Int, reader.ReadByte())
|
||||
};
|
||||
|
||||
SkillNameValue[] skills;
|
||||
|
||||
// We have protocol changes by now, so this is ok
|
||||
var skills = new SkillNameValue[state.NewCharacterCreation ? 4 : 3];
|
||||
skills[0] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
|
||||
skills[1] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
|
||||
skills[2] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
|
||||
if (state.NewCharacterCreation)
|
||||
{
|
||||
int is4 = reader.ReadByte();
|
||||
int vs4 = reader.ReadByte();
|
||||
|
||||
skills = new[]
|
||||
{
|
||||
new SkillNameValue((SkillName)is1, vs1),
|
||||
new SkillNameValue((SkillName)is2, vs2),
|
||||
new SkillNameValue((SkillName)is3, vs3),
|
||||
new SkillNameValue((SkillName)is4, vs4)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
skills = new[]
|
||||
{
|
||||
new SkillNameValue((SkillName)is1, vs1),
|
||||
new SkillNameValue((SkillName)is2, vs2),
|
||||
new SkillNameValue((SkillName)is3, vs3)
|
||||
};
|
||||
skills[3] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
|
||||
}
|
||||
|
||||
int hue = reader.ReadUInt16();
|
||||
|
|
@ -171,9 +151,7 @@ namespace Server.Network
|
|||
name,
|
||||
female,
|
||||
hue,
|
||||
str,
|
||||
dex,
|
||||
intl,
|
||||
stats,
|
||||
info[cityIndex],
|
||||
skills,
|
||||
shirtHue,
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ namespace Server.Network
|
|||
parentSerial = Serial.Zero;
|
||||
}
|
||||
|
||||
|
||||
var writer = new SpanWriter(stackalloc byte[15]);
|
||||
writer.Write((byte)0x2E); // Packet ID
|
||||
writer.Write(item.Serial);
|
||||
|
|
|
|||
|
|
@ -84,6 +84,14 @@ namespace Server.Network
|
|||
Listeners = listeners.ToArray();
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
foreach (var listener in Listeners)
|
||||
{
|
||||
listener.Server.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IPEndPoint> GetListeningAddresses(IPEndPoint ipep) =>
|
||||
NetworkInterface.GetAllNetworkInterfaces().SelectMany(adapter =>
|
||||
adapter.GetIPProperties().UnicastAddresses
|
||||
|
|
@ -93,12 +101,19 @@ namespace Server.Network
|
|||
|
||||
public static TcpListener CreateListener(IPEndPoint ipep)
|
||||
{
|
||||
var listener = new TcpListener(ipep);
|
||||
listener.Server.ExclusiveAddressUse = false;
|
||||
var listener = new TcpListener(ipep)
|
||||
{
|
||||
Server =
|
||||
{
|
||||
LingerState = new LingerOption(false, 0),
|
||||
ExclusiveAddressUse = true,
|
||||
NoDelay = true
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
listener.Start(8);
|
||||
listener.Start(32);
|
||||
return listener;
|
||||
}
|
||||
catch (SocketException se)
|
||||
|
|
@ -122,7 +137,7 @@ namespace Server.Network
|
|||
return null;
|
||||
}
|
||||
|
||||
public static int Slice()
|
||||
public static void Slice()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
|
|
@ -130,10 +145,7 @@ namespace Server.Network
|
|||
{
|
||||
Instances.Add(ns);
|
||||
ns.LogInfo("Connected. [{0} Online]", Instances.Count);
|
||||
ns.Start();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static async void BeginAcceptingSockets(this TcpListener listener)
|
||||
|
|
@ -184,7 +196,7 @@ namespace Server.Network
|
|||
}
|
||||
else
|
||||
{
|
||||
var ns = new NetState(new NetworkSocket(socket));
|
||||
var ns = new NetState(socket);
|
||||
_connectedQueue.Enqueue(ns);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ namespace Server
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Close() => _reader.Close();
|
||||
|
||||
public DateTime LastSerialized { get; init; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadString(bool intern = false)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ namespace Server
|
|||
|
||||
public override long Position => _position + Index;
|
||||
|
||||
|
||||
protected override int BufferSize => 81920;
|
||||
|
||||
public override void Flush()
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ namespace Server
|
|||
_encoding = encoding ?? TextEncoding.UTF8;
|
||||
}
|
||||
|
||||
public BufferReader(byte[] buffer, DateTime LastSerialized) : this(buffer) => LastSerialized = LastSerialized;
|
||||
|
||||
public void Reset(byte[] newBuffer, out byte[] oldBuffer)
|
||||
{
|
||||
oldBuffer = _buffer;
|
||||
|
|
@ -45,6 +47,8 @@ namespace Server
|
|||
}
|
||||
|
||||
// Compatible with BinaryReader.ReadString()
|
||||
public DateTime LastSerialized { get; init; } = DateTime.MinValue;
|
||||
|
||||
public string ReadString(bool intern = false)
|
||||
{
|
||||
if (!ReadBool())
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ namespace Server
|
|||
{
|
||||
var path = Path.Combine(savePath, name);
|
||||
|
||||
AssemblyHandler.EnsureDirectory(path);
|
||||
PathUtility.EnsureDirectory(path);
|
||||
|
||||
string binPath = Path.Combine(path, $"{name}.bin");
|
||||
using var bin = new BinaryFileWriter(binPath, true);
|
||||
|
|
@ -54,7 +54,7 @@ namespace Server
|
|||
{
|
||||
var path = Path.Combine(savePath, name);
|
||||
|
||||
AssemblyHandler.EnsureDirectory(path);
|
||||
PathUtility.EnsureDirectory(path);
|
||||
|
||||
string binPath = Path.Combine(path, $"{name}.bin");
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ namespace Server
|
|||
{
|
||||
public interface IGenericReader
|
||||
{
|
||||
// Used to determine valid Entity deserialization
|
||||
DateTime LastSerialized { get; init; }
|
||||
|
||||
string ReadString(bool intern = false);
|
||||
long ReadLong();
|
||||
ulong ReadULong();
|
||||
|
|
|
|||
|
|
@ -39,14 +39,13 @@ namespace Server
|
|||
|
||||
void Write(DateTime value)
|
||||
{
|
||||
var ticks = (value.Kind switch
|
||||
// If DateTimeKind is Unspecified, we can't assume it needs to be converted.
|
||||
if (value.Kind == DateTimeKind.Local)
|
||||
{
|
||||
DateTimeKind.Local => value.ToUniversalTime(),
|
||||
DateTimeKind.Unspecified => value.ToLocalTime().ToUniversalTime(),
|
||||
_ => value
|
||||
}).Ticks;
|
||||
value = value.ToUniversalTime();
|
||||
}
|
||||
|
||||
Write(ticks);
|
||||
Write(value.Ticks);
|
||||
}
|
||||
void WriteDeltaTime(DateTime value)
|
||||
{
|
||||
|
|
@ -59,17 +58,16 @@ namespace Server
|
|||
if (value == DateTime.MaxValue)
|
||||
{
|
||||
Write(long.MaxValue);
|
||||
return;
|
||||
}
|
||||
|
||||
var ticks = (value.Kind switch
|
||||
if (value.Kind == DateTimeKind.Local)
|
||||
{
|
||||
DateTimeKind.Local => value.ToUniversalTime(),
|
||||
DateTimeKind.Unspecified => value.ToLocalTime().ToUniversalTime(),
|
||||
_ => value
|
||||
}).Ticks;
|
||||
value = value.ToUniversalTime();
|
||||
}
|
||||
|
||||
// Technically supports negative deltas for times in the past
|
||||
Write(ticks - DateTime.UtcNow.Ticks);
|
||||
Write(value.Ticks - DateTime.UtcNow.Ticks);
|
||||
}
|
||||
void Write(IPAddress value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,10 +20,20 @@ namespace Server
|
|||
{
|
||||
public interface ISerializable
|
||||
{
|
||||
// Should be serialized/deserialized with the index so it can be referenced by IGenericReader
|
||||
DateTime Created { get; set; }
|
||||
|
||||
// Should be serialized/deserialized with the index so it can be referenced by IGenericReader
|
||||
DateTime LastSerialized { get; protected internal set; }
|
||||
long SavePosition { get; protected internal set; }
|
||||
BufferWriter SaveBuffer { get; protected internal set; }
|
||||
|
||||
int TypeRef { get; }
|
||||
Serial Serial { get; }
|
||||
|
||||
// Executed on every entity, before it's serialized.
|
||||
// For example, this is used to clean up weak references and mark them dirty.
|
||||
void BeforeSerialize();
|
||||
void Deserialize(IGenericReader reader);
|
||||
void Serialize(IGenericWriter writer);
|
||||
void Delete();
|
||||
|
|
@ -48,6 +58,8 @@ namespace Server
|
|||
{
|
||||
SaveBuffer ??= new BufferWriter(true);
|
||||
|
||||
BeforeSerialize();
|
||||
|
||||
// Clean, don't bother serializing
|
||||
if (SavePosition > -1)
|
||||
{
|
||||
|
|
@ -55,6 +67,7 @@ namespace Server
|
|||
return;
|
||||
}
|
||||
|
||||
LastSerialized = Core.Now;
|
||||
SaveBuffer.Seek(0, SeekOrigin.Begin);
|
||||
Serialize(SaveBuffer);
|
||||
|
||||
|
|
@ -64,7 +77,7 @@ namespace Server
|
|||
}
|
||||
else
|
||||
{
|
||||
SavePosition = -1;
|
||||
this.MarkDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,13 @@ namespace Server
|
|||
entity.SavePosition = -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Delete(this ISerializable entity, IEntity toDelete)
|
||||
{
|
||||
toDelete?.Delete();
|
||||
entity.MarkDirty();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Add<T>(this ISerializable entity, ICollection<T> list, T value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,31 +12,30 @@
|
|||
<Version>0.0.0</Version>
|
||||
</PropertyGroup>
|
||||
<Target Name="CleanPub" AfterTargets="Clean">
|
||||
<Message Text="Deleting source generated files..." />
|
||||
<Delete Files="Generated\**" ContinueOnError="true" />
|
||||
<Message Text="Removing distribution files..." />
|
||||
<Delete Files="..\..\Distribution\zlib.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\libz.dylib" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\libz.so" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\ZLib.Bindings.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Microsoft.Toolkit.HighPerformance.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Serilog.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Serilog.Sinks.Async.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Serilog.Sinks.Console.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\$(AssemblyName)" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\$(AssemblyName).deps.json" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\$(AssemblyName).dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\ref\$(AssemblyName).dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\$(AssemblyName).dll.config" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\$(AssemblyName).exe" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\$(AssemblyName).deps.json" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\$(AssemblyName).pdb" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\$(AssemblyName).runtimeconfig.dev.json" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\$(AssemblyName).runtimeconfig.json" ContinueOnError="true" />
|
||||
<Delete Files="Generated\**\*" ContinueOnError="true" />
|
||||
<RemoveDir Directories="Generated" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\libz.dylib" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\libz.so" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Microsoft.Toolkit.HighPerformance.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\PollGroup.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\ref\$(AssemblyName).dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Serilog.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Serilog.Sinks.Async.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Serilog.Sinks.Console.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\wepoll.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\ZLib.Bindings.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\zlib.dll" ContinueOnError="true" />
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.0.2" />
|
||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.0" />
|
||||
<PackageReference Include="PollGroup" Version="1.1.0" />
|
||||
<PackageReference Include="Zlib.Bindings" Version="1.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
@ -47,32 +46,7 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SerializationGenerator\SerializationGenerator.csproj">
|
||||
<SetTargetFramework>TargetFramework=netstandard2.0</SetTargetFramework>
|
||||
<OutputItemType>Analyzer</OutputItemType>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Migrations/*.v*.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Serialization\Attributes\SerializableFieldSaveValueDefaultAttribute.cs" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(RiderVersion)' != '' AND $([MSBuild]::VersionLessThan($(RiderVersion), '2021.2.0'))">
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
||||
</PropertyGroup>
|
||||
<Target Name="RemoveSourceGeneratedFiles" BeforeTargets="CoreCompile" Condition="'$(RiderVersion)' != '' AND $([MSBuild]::VersionLessThan($(RiderVersion), '2021.2.0'))">
|
||||
<ItemGroup>
|
||||
<Compile Remove="Generated\**" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<Target Name="AddSourceGeneratedFiles" AfterTargets="CoreCompile" Condition="'$(RiderVersion)' != '' AND $([MSBuild]::VersionLessThan($(RiderVersion), '2021.2.0'))">
|
||||
<ItemGroup>
|
||||
<Compile Include="Generated\**" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Server.Network;
|
||||
|
||||
|
|
@ -384,7 +385,8 @@ namespace Server
|
|||
{
|
||||
public SkillInfo(
|
||||
int skillID, string name, double strScale, double dexScale, double intScale, string title,
|
||||
SkillUseCallback callback, double strGain, double dexGain, double intGain, double gainFactor
|
||||
SkillUseCallback callback, double strGain, double dexGain, double intGain, double gainFactor,
|
||||
string professionSkillName = null
|
||||
)
|
||||
{
|
||||
Name = name;
|
||||
|
|
@ -398,7 +400,7 @@ namespace Server
|
|||
DexGain = dexGain;
|
||||
IntGain = intGain;
|
||||
GainFactor = gainFactor;
|
||||
|
||||
ProfessionSkillName = professionSkillName ?? Name.Replace(" ", "");
|
||||
StatTotal = strScale + dexScale + intScale;
|
||||
}
|
||||
|
||||
|
|
@ -426,25 +428,27 @@ namespace Server
|
|||
|
||||
public double GainFactor { get; set; }
|
||||
|
||||
public string ProfessionSkillName { get; set; }
|
||||
|
||||
public static SkillInfo[] Table { get; set; } =
|
||||
{
|
||||
new(0, "Alchemy", 0.0, 5.0, 5.0, "Alchemist", null, 0.0, 0.5, 0.5, 1.0),
|
||||
new(1, "Anatomy", 0.0, 0.0, 0.0, "Biologist", null, 0.15, 0.15, 0.7, 1.0),
|
||||
new(2, "Animal Lore", 0.0, 0.0, 0.0, "Naturalist", null, 0.0, 0.0, 1.0, 1.0),
|
||||
new(3, "Item Identification", 0.0, 0.0, 0.0, "Merchant", null, 0.0, 0.0, 1.0, 1.0),
|
||||
new(3, "Item Identification", 0.0, 0.0, 0.0, "Merchant", null, 0.0, 0.0, 1.0, 1.0, "ItemID"),
|
||||
new(4, "Arms Lore", 0.0, 0.0, 0.0, "Weapon Master", null, 0.75, 0.15, 0.1, 1.0),
|
||||
new(5, "Parrying", 7.5, 2.5, 0.0, "Duelist", null, 0.75, 0.25, 0.0, 1.0),
|
||||
new(6, "Begging", 0.0, 0.0, 0.0, "Beggar", null, 0.0, 0.0, 0.0, 1.0),
|
||||
new(7, "Blacksmithy", 10.0, 0.0, 0.0, "Blacksmith", null, 1.0, 0.0, 0.0, 1.0),
|
||||
new(8, "Bowcraft/Fletching", 6.0, 16.0, 0.0, "Bowyer", null, 0.6, 1.6, 0.0, 1.0),
|
||||
new(7, "Blacksmithy", 10.0, 0.0, 0.0, "Blacksmith", null, 1.0, 0.0, 0.0, 1.0, "Blacksmith"),
|
||||
new(8, "Bowcraft/Fletching", 6.0, 16.0, 0.0, "Bowyer", null, 0.6, 1.6, 0.0, 1.0, "Bowcraft"),
|
||||
new(9, "Peacemaking", 0.0, 0.0, 0.0, "Pacifier", null, 0.0, 0.0, 0.0, 1.0),
|
||||
new(10, "Camping", 20.0, 15.0, 15.0, "Explorer", null, 2.0, 1.5, 1.5, 1.0),
|
||||
new(11, "Carpentry", 20.0, 5.0, 0.0, "Carpenter", null, 2.0, 0.5, 0.0, 1.0),
|
||||
new(12, "Cartography", 0.0, 7.5, 7.5, "Cartographer", null, 0.0, 0.75, 0.75, 1.0),
|
||||
new(13, "Cooking", 0.0, 20.0, 30.0, "Chef", null, 0.0, 2.0, 3.0, 1.0),
|
||||
new(14, "Detecting Hidden", 0.0, 0.0, 0.0, "Scout", null, 0.0, 0.4, 0.6, 1.0),
|
||||
new(15, "Discordance", 0.0, 2.5, 2.5, "Demoralizer", null, 0.0, 0.25, 0.25, 1.0),
|
||||
new(16, "Evaluating Intelligence", 0.0, 0.0, 0.0, "Scholar", null, 0.0, 0.0, 1.0, 1.0),
|
||||
new(15, "Discordance", 0.0, 2.5, 2.5, "Demoralizer", null, 0.0, 0.25, 0.25, 1.0, "Enticement"),
|
||||
new(16, "Evaluating Intelligence", 0.0, 0.0, 0.0, "Scholar", null, 0.0, 0.0, 1.0, 1.0, "EvaluateIntelligence"),
|
||||
new(17, "Healing", 6.0, 6.0, 8.0, "Healer", null, 0.6, 0.6, 0.8, 1.0),
|
||||
new(18, "Fishing", 0.0, 0.0, 0.0, "Fisherman", null, 0.5, 0.5, 0.0, 1.0),
|
||||
new(19, "Forensic Evaluation", 0.0, 0.0, 0.0, "Detective", null, 0.0, 0.2, 0.8, 1.0),
|
||||
|
|
@ -476,7 +480,7 @@ namespace Server
|
|||
new(45, "Mining", 20.0, 0.0, 0.0, "Miner", null, 2.0, 0.0, 0.0, 1.0),
|
||||
new(46, "Meditation", 0.0, 0.0, 0.0, "Stoic", null, 0.0, 0.0, 0.0, 1.0),
|
||||
new(47, "Stealth", 0.0, 0.0, 0.0, "Rogue", null, 0.0, 0.0, 0.0, 1.0),
|
||||
new(48, "Remove Trap", 0.0, 0.0, 0.0, "Trap Specialist", null, 0.0, 0.0, 0.0, 1.0),
|
||||
new(48, "Remove Trap", 0.0, 0.0, 0.0, "Trap Specialist", null, 0.0, 0.0, 0.0, 1.0, "Disarm"),
|
||||
new(49, "Necromancy", 0.0, 0.0, 0.0, "Necromancer", null, 0.0, 0.0, 0.0, 1.0),
|
||||
new(50, "Focus", 0.0, 0.0, 0.0, "Driven", null, 0.0, 0.0, 0.0, 1.0),
|
||||
new(51, "Chivalry", 0.0, 0.0, 0.0, "Paladin", null, 0.0, 0.0, 0.0, 1.0),
|
||||
|
|
@ -892,16 +896,16 @@ namespace Server
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SkillsEnumerator GetEnumerator() => new(m_Skills);
|
||||
public Enumerator GetEnumerator() => new(m_Skills);
|
||||
|
||||
public ref struct SkillsEnumerator
|
||||
public ref struct Enumerator
|
||||
{
|
||||
private readonly Skill[] _skills;
|
||||
private int _index;
|
||||
private Skill _current;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal SkillsEnumerator(Skill[] skills)
|
||||
internal Enumerator(Skill[] skills)
|
||||
{
|
||||
_skills = skills;
|
||||
_index = 0;
|
||||
|
|
|
|||
|
|
@ -169,6 +169,28 @@ namespace Server
|
|||
return str;
|
||||
}
|
||||
|
||||
public static string TrimMultiline(this string str, string lineSeparator = "\n")
|
||||
{
|
||||
var parts = str.Split(lineSeparator);
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
{
|
||||
parts[i] = parts[i].Trim();
|
||||
}
|
||||
|
||||
return string.Join(lineSeparator, parts);
|
||||
}
|
||||
|
||||
public static string IndentMultiline(this string str, string indent = "\t", string lineSeparator = "\n")
|
||||
{
|
||||
var parts = str.Split(lineSeparator);
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
{
|
||||
parts[i] = $"{indent}{parts[i]}";
|
||||
}
|
||||
|
||||
return string.Join(lineSeparator, parts);
|
||||
}
|
||||
|
||||
public static List<string> Wrap(this string value, int perLine, int maxLines)
|
||||
{
|
||||
if ((value = value?.Trim() ?? "").Length <= 0)
|
||||
|
|
|
|||
|
|
@ -104,11 +104,28 @@ namespace Server
|
|||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public int CalcHeight => (Flags & TileFlag.Bridge) != 0 ? _height / 2 : _height;
|
||||
public int CalcHeight => Bridge ? _height / 2 : _height;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public bool Door
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.Door];
|
||||
set => this[TileFlag.Door] = value;
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public bool Background
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.Background];
|
||||
set => this[TileFlag.Background] = value;
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public bool Bridge
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.Bridge];
|
||||
set => this[TileFlag.Bridge] = value;
|
||||
}
|
||||
|
|
@ -116,6 +133,7 @@ namespace Server
|
|||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public bool Wall
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.Wall];
|
||||
set => this[TileFlag.Wall] = value;
|
||||
}
|
||||
|
|
@ -123,13 +141,22 @@ namespace Server
|
|||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public bool Window
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.Window];
|
||||
set => this[TileFlag.Window] = value;
|
||||
}
|
||||
|
||||
public bool ImpassableSurface
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.Impassable | TileFlag.Surface];
|
||||
set => this[TileFlag.Impassable | TileFlag.Surface] = value;
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public bool Impassable
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.Impassable];
|
||||
set => this[TileFlag.Impassable] = value;
|
||||
}
|
||||
|
|
@ -137,6 +164,7 @@ namespace Server
|
|||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public bool Surface
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.Surface];
|
||||
set => this[TileFlag.Surface] = value;
|
||||
}
|
||||
|
|
@ -144,6 +172,7 @@ namespace Server
|
|||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public bool Roof
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.Roof];
|
||||
set => this[TileFlag.Roof] = value;
|
||||
}
|
||||
|
|
@ -151,10 +180,19 @@ namespace Server
|
|||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public bool LightSource
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.LightSource];
|
||||
set => this[TileFlag.LightSource] = value;
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
|
||||
public bool Wet
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[TileFlag.Wet];
|
||||
set => this[TileFlag.Wet] = value;
|
||||
}
|
||||
|
||||
public bool this[TileFlag flag]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
|
|
|||
80
Projects/Server/TimeZones/TimeZoneHandler.cs
Normal file
80
Projects/Server/TimeZones/TimeZoneHandler.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: TimeZoneHandler.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Server.Json;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
/**
|
||||
* TimeZoneHandler provides a cross platform compatible way to handle system timezones.
|
||||
*
|
||||
* By default the system timezone is used.
|
||||
* To manually configure the local timezone add the following setting to modernuo.json:
|
||||
* "system.localTimeZone": "Pacific Standard Time"
|
||||
* All valid timezones can be found in the Distribution/Data/timezones.json file.
|
||||
*
|
||||
* Notes for timezones.json:
|
||||
* By default, Windows will use the values in the "timezone" property.
|
||||
* By default, Unix/MacOS will use the values in the "region" property.
|
||||
* system.localTimeZone can be either timezone or a region.
|
||||
*
|
||||
* Example:
|
||||
* "system.localTimeZone": "Europe/Lisbon"
|
||||
*/
|
||||
public static class TimeZoneHandler
|
||||
{
|
||||
private static readonly Dictionary<string, TimeZoneInfo> _timeZoneById = new();
|
||||
public static TimeZoneInfo SystemTimeZone { get; private set; }
|
||||
|
||||
static TimeZoneHandler()
|
||||
{
|
||||
var timezones = JsonConfig.Deserialize<TimeZoneWithRegions[]>(Path.Combine(Core.BaseDirectory, "Data/timezones.json"));
|
||||
foreach (var tz in timezones)
|
||||
{
|
||||
// Get the timezone if we are on Windows
|
||||
var tzInfo = Core.IsWindows ? TimeZoneInfo.FindSystemTimeZoneById(tz.TimeZone) : null;
|
||||
|
||||
foreach (var region in tz.Regions)
|
||||
{
|
||||
// Get the timezone by region if we are on linux
|
||||
tzInfo ??= TimeZoneInfo.FindSystemTimeZoneById(region);
|
||||
_timeZoneById[region] = tzInfo;
|
||||
}
|
||||
|
||||
_timeZoneById[tz.TimeZone] = tzInfo;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
var tzId = ServerConfiguration.GetSetting("system.localTimeZone", TimeZoneInfo.Local.Id);
|
||||
SystemTimeZone = FindTimeZoneById(tzId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross platform version of TimeZoneInfo.FindSystemTimeZoneById
|
||||
*/
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TimeZoneInfo FindTimeZoneById(string id) => _timeZoneById[id];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DateTime ToSystemLocalTime(this DateTime date) =>
|
||||
TimeZoneInfo.ConvertTimeFromUtc(date, SystemTimeZone);
|
||||
}
|
||||
}
|
||||
28
Projects/Server/TimeZones/TimeZoneWithRegions.cs
Normal file
28
Projects/Server/TimeZones/TimeZoneWithRegions.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: TimeZoneWithRegions.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public record TimeZoneWithRegions
|
||||
{
|
||||
[JsonPropertyName("timezone")]
|
||||
public string TimeZone { get; init; }
|
||||
|
||||
[JsonPropertyName("regions")]
|
||||
public string[] Regions { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ namespace Server
|
|||
public static void StartTimer(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
||||
{
|
||||
DelayCallTimer t = DelayCallTimer.GetTimer(delay, interval, count, callback);
|
||||
t._selfReturn = true;
|
||||
t._returnOnDetach = true;
|
||||
t.Start();
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
|
|
@ -115,7 +115,7 @@ namespace Server
|
|||
|
||||
public sealed class DelayCallTimer : Timer, INotifyCompletion
|
||||
{
|
||||
internal bool _selfReturn;
|
||||
internal bool _returnOnDetach;
|
||||
#if DEBUG_TIMERS
|
||||
internal bool _allowFinalization;
|
||||
#endif
|
||||
|
|
@ -143,57 +143,50 @@ namespace Server
|
|||
_continuation?.Invoke();
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
base.Stop();
|
||||
|
||||
if (_selfReturn)
|
||||
{
|
||||
Return();
|
||||
}
|
||||
}
|
||||
|
||||
internal void Return()
|
||||
internal override void OnDetach()
|
||||
{
|
||||
if (Running)
|
||||
{
|
||||
logger.Error($"Timer is returned while still running! {new StackTrace()}");
|
||||
logger.Error($"Timer is returned while still running!\n{new StackTrace()}");
|
||||
return;
|
||||
}
|
||||
|
||||
Version++; // Increment the version so if this is called from OnTick() and another timer is started, we don't have a problem
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
_stackTraces.Remove(GetHashCode());
|
||||
#endif
|
||||
|
||||
if (_poolCount >= _poolCapacity)
|
||||
if (_returnOnDetach)
|
||||
{
|
||||
#if DEBUG_TIMERS
|
||||
logger.Warning($"DelayCallTimer pool reached maximum of {_poolCapacity} timers");
|
||||
_allowFinalization = true;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
// Increment the version so if this is called from OnTick() and another timer is started, we don't have a problem
|
||||
// Version++;
|
||||
|
||||
_continuation = null;
|
||||
ReturnToPool(1, this, this);
|
||||
#if DEBUG_TIMERS
|
||||
_stackTraces.Remove(GetHashCode());
|
||||
#endif
|
||||
|
||||
if (_poolCount >= _poolCapacity)
|
||||
{
|
||||
#if DEBUG_TIMERS
|
||||
logger.Warning($"DelayCallTimer pool reached maximum of {_poolCapacity} timers");
|
||||
_allowFinalization = true;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
_continuation = null;
|
||||
_returnOnDetach = false;
|
||||
ReturnToPool(1, this, this);
|
||||
}
|
||||
}
|
||||
|
||||
public static DelayCallTimer GetTimer(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
||||
{
|
||||
if (_poolHead != null)
|
||||
var timer = GetFromPool();
|
||||
if (timer != null)
|
||||
{
|
||||
_poolCount--;
|
||||
#if DEBUG_TIMERS
|
||||
logger.Information($"Pool count: {_poolCount} / {_poolCapacity}");
|
||||
logger.Information($"Getting from pool: ({_poolCount} / {_poolCapacity})");
|
||||
#endif
|
||||
|
||||
var timer = GetFromPool();
|
||||
|
||||
timer.Init(delay, interval, count);
|
||||
timer._continuation = callback;
|
||||
timer._selfReturn = false;
|
||||
timer._returnOnDetach = false;
|
||||
#if DEBUG_TIMERS
|
||||
timer._allowFinalization = false;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -62,22 +62,29 @@ namespace Server
|
|||
_poolHead = head;
|
||||
_poolCount += amount;
|
||||
#if DEBUG_TIMERS
|
||||
logger.Information($"Pool count: {_poolCount} / {_poolCapacity}");
|
||||
logger.Information($"Returning to pool. ({_poolCount} / {_poolCapacity})");
|
||||
#endif
|
||||
}
|
||||
|
||||
private static DelayCallTimer GetFromPool()
|
||||
{
|
||||
if (_poolHead == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var timer = _poolHead;
|
||||
_poolHead = _poolHead._nextTimer as DelayCallTimer;
|
||||
timer.Detach();
|
||||
_poolCount--;
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
internal static void RefillPool(int amount, out DelayCallTimer head, out DelayCallTimer tail)
|
||||
{
|
||||
#if DEBUG_TIMERS
|
||||
logger.Information($"Filling pool with {amount} timers.");
|
||||
logger.Information($"Filling pool with {amount} timers.");
|
||||
#endif
|
||||
|
||||
head = null;
|
||||
|
|
|
|||
|
|
@ -28,11 +28,12 @@ namespace Server
|
|||
private const int _tickRatePowerOf2 = 3;
|
||||
private const int _tickRate = 1 << _tickRatePowerOf2; // 8ms
|
||||
|
||||
private static long _lastTickTurned = -1;
|
||||
|
||||
private static readonly Timer[][] _rings = new Timer[_ringLayers][];
|
||||
private static readonly int[] _ringIndexes = new int[_ringLayers];
|
||||
|
||||
private static long _lastTickTurned = -1;
|
||||
private static bool _timerWheelExecuting;
|
||||
|
||||
public static void Init(long tickCount)
|
||||
{
|
||||
_lastTickTurned = tickCount;
|
||||
|
|
@ -44,46 +45,63 @@ namespace Server
|
|||
}
|
||||
}
|
||||
|
||||
public static int Slice(long tickCount)
|
||||
public static void Slice(long tickCount)
|
||||
{
|
||||
var deltaSinceTurn = tickCount - _lastTickTurned;
|
||||
var events = 0;
|
||||
while (deltaSinceTurn >= _tickRate)
|
||||
{
|
||||
deltaSinceTurn -= _tickRate;
|
||||
_lastTickTurned += _tickRate;
|
||||
events += Turn() ? 1 : 0;
|
||||
Turn();
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
private static bool Turn()
|
||||
private static void Turn()
|
||||
{
|
||||
bool events = false;
|
||||
_timerWheelExecuting = true;
|
||||
var turnNextWheel = false;
|
||||
|
||||
var _executingRings = new Timer[_ringLayers];
|
||||
|
||||
// Detach the chain from the timer wheel. This allows adding timers to the same slot during execution.
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
{
|
||||
if (i == 0 || turnNextWheel)
|
||||
{
|
||||
var ringIndex = ++_ringIndexes[i];
|
||||
turnNextWheel = ringIndex >= _ringSize;
|
||||
|
||||
if (turnNextWheel)
|
||||
{
|
||||
ringIndex = _ringIndexes[i] = 0;
|
||||
}
|
||||
|
||||
_executingRings[i] = _rings[i][ringIndex];
|
||||
_rings[i][ringIndex] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_executingRings[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
{
|
||||
// Increment the ring index, then get the timer.
|
||||
var ringIndex = ++_ringIndexes[i];
|
||||
bool turnNextWheel = ringIndex >= _ringSize;
|
||||
|
||||
if (turnNextWheel)
|
||||
var timer = _executingRings[i];
|
||||
if (timer == null)
|
||||
{
|
||||
ringIndex = _ringIndexes[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
var timer = _rings[i][ringIndex];
|
||||
if (timer != null)
|
||||
do
|
||||
{
|
||||
events = true;
|
||||
var next = timer._nextTimer;
|
||||
|
||||
do
|
||||
timer.Detach();
|
||||
|
||||
// Check to see if it's running just in case it was stopped by another timer
|
||||
if (timer.Running)
|
||||
{
|
||||
var next = _rings[i][ringIndex] = timer._nextTimer;
|
||||
|
||||
timer.Detach();
|
||||
|
||||
if (i > 0 && timer._remaining > 0)
|
||||
{
|
||||
// Promote
|
||||
|
|
@ -93,18 +111,18 @@ namespace Server
|
|||
{
|
||||
Execute(timer);
|
||||
}
|
||||
}
|
||||
|
||||
timer = next;
|
||||
} while (timer != null);
|
||||
}
|
||||
if (!timer.Running)
|
||||
{
|
||||
timer.OnDetach();
|
||||
}
|
||||
|
||||
if (!turnNextWheel)
|
||||
{
|
||||
break;
|
||||
}
|
||||
timer = next;
|
||||
} while (timer != null);
|
||||
}
|
||||
|
||||
return events;
|
||||
_timerWheelExecuting = false;
|
||||
}
|
||||
|
||||
private static void Execute(Timer timer)
|
||||
|
|
@ -136,14 +154,17 @@ namespace Server
|
|||
|
||||
private static void AddTimer(Timer timer, long delay)
|
||||
{
|
||||
#if DEBUG_TIMERS
|
||||
var originalDelay = delay;
|
||||
#endif
|
||||
delay = Math.Max(0, delay);
|
||||
|
||||
var resolutionPowerOf2 = _tickRatePowerOf2;
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
{
|
||||
var resolution = 1 << resolutionPowerOf2;
|
||||
var resolution = 1L << resolutionPowerOf2;
|
||||
var nextResolutionPowerOf2 = resolutionPowerOf2 + _ringSizePowerOf2;
|
||||
long max = 1 << nextResolutionPowerOf2;
|
||||
var max = 1L << nextResolutionPowerOf2;
|
||||
if (delay < max)
|
||||
{
|
||||
var remaining = delay & (resolution - 1);
|
||||
|
|
@ -168,13 +189,18 @@ namespace Server
|
|||
|
||||
_rings[i][slot] = timer;
|
||||
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
// The remaining amount until we turn this ring
|
||||
delay -= resolution * (_ringSize - _ringIndexes[i]);
|
||||
resolutionPowerOf2 = nextResolutionPowerOf2;
|
||||
}
|
||||
|
||||
// TODO: Handle timers > 17yrs
|
||||
#if DEBUG_TIMERS
|
||||
logger.Error($"Timer is more than max duration. ({originalDelay})");
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void DumpInfo(TextWriter tw)
|
||||
|
|
@ -216,7 +242,7 @@ namespace Server
|
|||
}
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
tw.WriteLine("\nStack Traces:");
|
||||
tw.WriteLine($"{Environment.NewLine}Stack Traces:");
|
||||
foreach (var kvp in DelayCallTimer._stackTraces)
|
||||
{
|
||||
tw.WriteLine(kvp.Value);
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ namespace Server
|
|||
private Timer _nextTimer;
|
||||
private Timer _prevTimer;
|
||||
|
||||
public Timer(TimeSpan delay) : this(delay, TimeSpan.Zero, 1)
|
||||
{
|
||||
}
|
||||
public Timer(TimeSpan delay) => Init(delay, TimeSpan.Zero, 1);
|
||||
|
||||
public Timer(TimeSpan interval, int count) => Init(interval, interval, count);
|
||||
|
||||
public Timer(TimeSpan delay, TimeSpan interval, int count = 0) => Init(delay, interval, count);
|
||||
|
||||
|
|
@ -102,17 +102,22 @@ namespace Server
|
|||
return;
|
||||
}
|
||||
|
||||
// We are at the head
|
||||
if (_rings[_ring][_slot] == this)
|
||||
// Do not detach if we are in the middle of executing the timer wheel for this ring/slot
|
||||
if (!_timerWheelExecuting || _ringIndexes[_ring] != _slot)
|
||||
{
|
||||
_rings[_ring][_slot] = _nextTimer;
|
||||
// We are at the head
|
||||
if (_rings[_ring][_slot] == this)
|
||||
{
|
||||
_rings[_ring][_slot] = _nextTimer;
|
||||
}
|
||||
|
||||
Detach();
|
||||
}
|
||||
|
||||
Detach();
|
||||
Running = false;
|
||||
Version++;
|
||||
var prof = GetProfile();
|
||||
|
||||
var prof = GetProfile();
|
||||
if (prof != null)
|
||||
{
|
||||
prof.Stopped++;
|
||||
|
|
@ -147,5 +152,9 @@ namespace Server
|
|||
_nextTimer = null;
|
||||
_prevTimer = null;
|
||||
}
|
||||
|
||||
internal virtual void OnDetach()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,9 +50,12 @@ namespace Server
|
|||
|
||||
public void Cancel()
|
||||
{
|
||||
_timer?.Stop();
|
||||
_timer?.Return();
|
||||
_timer = null;
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer._returnOnDetach = true;
|
||||
_timer?.Stop();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
this = default;
|
||||
}
|
||||
|
|
|
|||
67
Projects/Server/Utilities/PathUtility.cs
Normal file
67
Projects/Server/Utilities/PathUtility.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: PathUtility.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Server.Text;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public static class PathUtility
|
||||
{
|
||||
public static string EnsureDirectory(string dir)
|
||||
{
|
||||
var path = GetFullPath(dir, Core.BaseDirectory);
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static void EnsureDirectory(this FileInfo fi)
|
||||
{
|
||||
var dir = GetFullPath(fi.DirectoryName, Core.BaseDirectory);
|
||||
if (dir != null)
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnsureDirectory(this DirectoryInfo di)
|
||||
{
|
||||
var file = GetFullPath(di.FullName, Core.BaseDirectory);
|
||||
Directory.CreateDirectory(file);
|
||||
}
|
||||
|
||||
public static string GetFullPath(string relativeOrAbsolutePath) =>
|
||||
GetFullPath(relativeOrAbsolutePath, Core.BaseDirectory);
|
||||
|
||||
public static string GetFullPath(string relativeOrAbsolutePath, string basePath) =>
|
||||
relativeOrAbsolutePath switch
|
||||
{
|
||||
null => null,
|
||||
"" => basePath,
|
||||
_ => Path.IsPathRooted(relativeOrAbsolutePath)
|
||||
? relativeOrAbsolutePath
|
||||
: Path.GetFullPath(relativeOrAbsolutePath, basePath)
|
||||
};
|
||||
|
||||
public static string EnsureRandomPath(string basePath)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[8];
|
||||
Utility.RandomBytes(bytes);
|
||||
return EnsureDirectory(Path.Combine(basePath, bytes.ToHexString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -484,7 +484,6 @@ namespace Server
|
|||
break;
|
||||
}
|
||||
|
||||
|
||||
num = BinaryPrimitives.ReadUInt16BigEndian(ip.Slice(byteIndex, 2));
|
||||
byteIndex += 2;
|
||||
|
||||
|
|
@ -927,14 +926,11 @@ namespace Server
|
|||
return outputList;
|
||||
}
|
||||
|
||||
public static bool ToBoolean(string value)
|
||||
{
|
||||
#pragma warning disable CA1806 // Do not ignore method results
|
||||
bool.TryParse(value, out var b);
|
||||
#pragma warning restore CA1806 // Do not ignore method results
|
||||
|
||||
return b;
|
||||
}
|
||||
public static bool ToBoolean(string value) =>
|
||||
bool.TryParse(value, out var b) && b ||
|
||||
value.InsensitiveEquals("enabled") ||
|
||||
value.InsensitiveEquals("on") ||
|
||||
!value.InsensitiveEquals("disabled") && !value.InsensitiveEquals("off");
|
||||
|
||||
public static double ToDouble(string value)
|
||||
{
|
||||
|
|
@ -1335,28 +1331,6 @@ namespace Server
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T Max<T>(T val, T max) where T : IComparable<T> => val.CompareTo(max) > 0 ? val : max;
|
||||
|
||||
public static string TrimMultiline(this string str, string lineSeparator = "\n")
|
||||
{
|
||||
var parts = str.Split(lineSeparator);
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
{
|
||||
parts[i] = parts[i].Trim();
|
||||
}
|
||||
|
||||
return string.Join(lineSeparator, parts);
|
||||
}
|
||||
|
||||
public static string IndentMultiline(this string str, string indent = "\t", string lineSeparator = "\n")
|
||||
{
|
||||
var parts = str.Split(lineSeparator);
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
{
|
||||
parts[i] = $"{indent}{parts[i]}";
|
||||
}
|
||||
|
||||
return string.Join(lineSeparator, parts);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Tidy<T>(this List<T> list) where T : ISerializable
|
||||
{
|
||||
|
|
@ -1484,7 +1458,10 @@ namespace Server
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetTimeStamp() => Core.Now.ToString("yyyy-MM-dd-HH-mm-ss");
|
||||
public static string GetTimeStamp() => Core.Now.ToTimeStamp();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string ToTimeStamp(this DateTime dt) => dt.ToString("yyyy-MM-dd-HH-mm-ss");
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Add<T>(ref List<T> list, T value)
|
||||
|
|
@ -1639,5 +1616,26 @@ namespace Server
|
|||
dict.Clear();
|
||||
dict = null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void DateToComponents(
|
||||
DateTime date,
|
||||
out int year,
|
||||
out int month,
|
||||
out int day,
|
||||
out DayOfWeek dayOfWeek,
|
||||
out int hour,
|
||||
out int min,
|
||||
out int sec
|
||||
)
|
||||
{
|
||||
year = date.Year;
|
||||
month = date.Month;
|
||||
day = date.Day;
|
||||
dayOfWeek = date.DayOfWeek;
|
||||
hour = date.Hour;
|
||||
min = date.Minute;
|
||||
sec = date.Second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ namespace Server
|
|||
{
|
||||
public static class EntityPersistence
|
||||
{
|
||||
private const int _idxVersion = 1;
|
||||
|
||||
public static void WriteEntities<I, T>(
|
||||
IIndexInfo<I> indexInfo,
|
||||
Dictionary<I, T> entities,
|
||||
|
|
@ -38,7 +40,7 @@ namespace Server
|
|||
|
||||
var path = Path.Combine(savePath, typeName);
|
||||
|
||||
AssemblyHandler.EnsureDirectory(path);
|
||||
PathUtility.EnsureDirectory(path);
|
||||
|
||||
string idxPath = Path.Combine(path, $"{typeName}.idx");
|
||||
string tdbPath = Path.Combine(path, $"{typeName}.tdb");
|
||||
|
|
@ -48,6 +50,7 @@ namespace Server
|
|||
using var tdb = new BinaryFileWriter(tdbPath, false);
|
||||
using var bin = new BinaryFileWriter(binPath, true);
|
||||
|
||||
idx.Write(1); // Version
|
||||
idx.Write(entities.Count);
|
||||
foreach (var e in entities.Values)
|
||||
{
|
||||
|
|
@ -55,6 +58,8 @@ namespace Server
|
|||
|
||||
idx.Write(e.TypeRef);
|
||||
idx.Write(e.Serial);
|
||||
idx.Write(e.Created.Ticks);
|
||||
idx.Write(e.LastSerialized.Ticks);
|
||||
idx.Write(start);
|
||||
|
||||
e.SerializeTo(bin);
|
||||
|
|
@ -110,12 +115,28 @@ namespace Server
|
|||
|
||||
List<Tuple<ConstructorInfo, string>> types = ReadTypes<I>(tdbReader);
|
||||
|
||||
var count = idxReader.ReadInt32();
|
||||
int count;
|
||||
var version = idxReader.ReadInt32();
|
||||
|
||||
// Handle non-versioned (version 0).
|
||||
if (version > _idxVersion || idx.Length - 4 - version * 20 == 0)
|
||||
{
|
||||
count = version;
|
||||
version = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = idxReader.ReadInt32();
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
var typeID = idxReader.ReadInt32();
|
||||
var number = idxReader.ReadUInt32();
|
||||
var serial = idxReader.ReadUInt32();
|
||||
var created = version == 0 ? now : new DateTime(idxReader.ReadInt64(), DateTimeKind.Utc);
|
||||
var lastSerialized = version == 0 ? DateTime.MinValue : new DateTime(idxReader.ReadInt64(), DateTimeKind.Utc);
|
||||
var pos = idxReader.ReadInt64();
|
||||
var length = idxReader.ReadInt32();
|
||||
|
||||
|
|
@ -127,12 +148,14 @@ namespace Server
|
|||
}
|
||||
|
||||
ConstructorInfo ctor = objs.Item1;
|
||||
I indexer = indexInfo.CreateIndex(number);
|
||||
I indexer = indexInfo.CreateIndex(serial);
|
||||
|
||||
ctorArgs[0] = indexer;
|
||||
|
||||
if (ctor.Invoke(ctorArgs) is T t)
|
||||
{
|
||||
t.Created = created;
|
||||
t.LastSerialized = lastSerialized;
|
||||
entities.Add(new EntityIndex<T>(t, typeID, pos, length));
|
||||
map[indexer] = t;
|
||||
}
|
||||
|
|
@ -180,7 +203,7 @@ namespace Server
|
|||
var buffer = GC.AllocateUninitializedArray<byte>(entry.Length);
|
||||
if (br == null)
|
||||
{
|
||||
br = new BufferReader(buffer);
|
||||
br = new BufferReader(buffer, t.LastSerialized);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ namespace Server
|
|||
private static readonly ConcurrentQueue<Item> _decayQueue = new();
|
||||
|
||||
private static string _tempSavePath; // Path to the temporary folder for the save
|
||||
private static string _savePath; // Path to "Saves" folder
|
||||
private static bool _enableSaveStats;
|
||||
|
||||
public const bool DirtyTrackingEnabled = false;
|
||||
public const uint ItemOffset = 0x40000000;
|
||||
|
|
@ -129,9 +129,11 @@ namespace Server
|
|||
internal static List<Type> MobileTypes { get; } = new();
|
||||
internal static List<Type> GuildTypes { get; } = new();
|
||||
|
||||
public static string SavePath { get; private set; }
|
||||
|
||||
public static WorldState WorldState { get; private set; }
|
||||
public static bool Saving => WorldState == WorldState.Saving;
|
||||
public static bool Running => WorldState != WorldState.Loading && WorldState != WorldState.Initial;
|
||||
public static bool Running => WorldState is not WorldState.Loading and not WorldState.Initial;
|
||||
public static bool Loading => WorldState == WorldState.Loading;
|
||||
|
||||
public static Dictionary<Serial, Mobile> Mobiles { get; private set; }
|
||||
|
|
@ -140,10 +142,13 @@ namespace Server
|
|||
|
||||
public static void Configure()
|
||||
{
|
||||
var tempSavePath = ServerConfiguration.GetOrUpdateSetting("world.tempSavePath", "temp");
|
||||
_tempSavePath = Path.Combine(Core.BaseDirectory, tempSavePath);
|
||||
var tempSavePath = ServerConfiguration.GetSetting("world.tempSavePath", "temp");
|
||||
_tempSavePath = PathUtility.GetFullPath(tempSavePath);
|
||||
|
||||
var savePath = ServerConfiguration.GetOrUpdateSetting("world.savePath", "Saves");
|
||||
_savePath = Path.Combine(Core.BaseDirectory, savePath);
|
||||
SavePath = PathUtility.GetFullPath(savePath);
|
||||
|
||||
_enableSaveStats = ServerConfiguration.GetOrUpdateSetting("world.enableSaveStats", false);
|
||||
|
||||
// Mobiles & Items
|
||||
Persistence.Register("Mobiles & Items", SaveEntities, WriteEntities, LoadEntities, 1);
|
||||
|
|
@ -254,7 +259,7 @@ namespace Server
|
|||
logger.Information("Loading world");
|
||||
var watch = Stopwatch.StartNew();
|
||||
|
||||
Persistence.Load(_savePath);
|
||||
Persistence.Load(SavePath);
|
||||
EventSink.InvokeWorldLoad();
|
||||
|
||||
ProcessSafetyQueues();
|
||||
|
|
@ -341,7 +346,10 @@ namespace Server
|
|||
int count = 0;
|
||||
|
||||
var timestamp = Utility.GetTimeStamp();
|
||||
using var op = new StreamWriter("Logs/Saves/Save-Stats-{0}.log", true);
|
||||
var saveStatsPath = Path.Combine(Core.BaseDirectory, $"Logs/Saves/Save-Stats-{timestamp}.log");
|
||||
PathUtility.EnsureDirectory(saveStatsPath);
|
||||
|
||||
using var op = new StreamWriter(saveStatsPath, true);
|
||||
|
||||
for (var i = 0; i < entityTypes.Length; i++)
|
||||
{
|
||||
|
|
@ -373,14 +381,17 @@ namespace Server
|
|||
EntityPersistence.WriteEntities(itemIndexInfo, Items, ItemTypes, basePath, out var itemCounts);
|
||||
EntityPersistence.WriteEntities(guildIndexInfo, Guilds, GuildTypes, basePath, out var guildCounts);
|
||||
|
||||
TraceSave(mobileCounts?.ToList(), itemCounts?.ToList(), guildCounts?.ToList());
|
||||
if (_enableSaveStats)
|
||||
{
|
||||
TraceSave(mobileCounts?.ToList(), itemCounts?.ToList(), guildCounts?.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteFiles(object state)
|
||||
{
|
||||
Exception exception = null;
|
||||
|
||||
var tempPath = Path.Combine(_tempSavePath, Utility.GetTimeStamp());
|
||||
var tempPath = PathUtility.EnsureRandomPath(_tempSavePath);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -409,8 +420,8 @@ namespace Server
|
|||
{
|
||||
try
|
||||
{
|
||||
EventSink.InvokeWorldSavePostSnapshot(_savePath, tempPath);
|
||||
Directory.Move(tempPath, _savePath);
|
||||
EventSink.InvokeWorldSavePostSnapshot(SavePath, tempPath);
|
||||
Directory.Move(tempPath, SavePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -652,13 +663,24 @@ namespace Server
|
|||
Serial serial = reader.ReadSerial();
|
||||
var typeT = typeof(T);
|
||||
|
||||
T entity;
|
||||
|
||||
// Add to this list when creating new serializable types
|
||||
if (typeof(BaseGuild).IsAssignableTo(typeT))
|
||||
{
|
||||
return FindGuild(serial) as T;
|
||||
entity = FindGuild(serial) as T;
|
||||
}
|
||||
else
|
||||
{
|
||||
entity = FindEntity<IEntity>(serial) as T;
|
||||
}
|
||||
|
||||
return FindEntity<IEntity>(serial) as T;
|
||||
if (entity?.Deleted == false)
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
return entity?.Created <= reader.LastSerialized ? entity : null;
|
||||
}
|
||||
|
||||
public static List<T> ReadEntityList<T>(this IGenericReader reader) where T : class, ISerializable
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Tests
|
||||
|
|
@ -8,8 +9,8 @@ namespace Server.Tests
|
|||
// Global setup
|
||||
static ServerFixture()
|
||||
{
|
||||
Core.Assembly = Assembly.GetExecutingAssembly();
|
||||
Core.LoopContext = new EventLoopContext();
|
||||
|
||||
Core.Expansion = Expansion.EJ;
|
||||
|
||||
// Load Configurations
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
|
|
|
|||
102
Projects/UOContent/Accounting/Account.Migrations.cs
Normal file
102
Projects/UOContent/Accounting/Account.Migrations.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Server.Accounting.Security;
|
||||
|
||||
namespace Server.Accounting
|
||||
{
|
||||
public partial class Account
|
||||
{
|
||||
private void MigrateFrom(V3Content content)
|
||||
{
|
||||
_username = content.Username;
|
||||
_passwordAlgorithm = content.PasswordAlgorithm;
|
||||
_password = content.Password;
|
||||
_accessLevel = content.AccessLevel;
|
||||
_flags = content.Flags;
|
||||
Created = content.Created;
|
||||
_lastLogin = content.LastLogin;
|
||||
_totalGold = content.TotalGold;
|
||||
_totalPlat = content.TotalPlat;
|
||||
_mobiles = content.Mobiles;
|
||||
_comments = content.Comments;
|
||||
_tags = content.Tags;
|
||||
_loginIPs = content.LoginIPs;
|
||||
_ipRestrictions = content.IpRestrictions;
|
||||
_totalGameTime = content.TotalGameTime;
|
||||
_email = content.Email;
|
||||
}
|
||||
|
||||
private void Deserialize(IGenericReader reader, int version)
|
||||
{
|
||||
if (version != 2)
|
||||
{
|
||||
// Due to a bug where we were not versioning at all, reset so we don't have an issue deserializing
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
_username = reader.ReadString();
|
||||
_passwordAlgorithm = version < 2 ? (PasswordProtectionAlgorithm)reader.ReadInt() : reader.ReadEnum<PasswordProtectionAlgorithm>();
|
||||
_password = reader.ReadString();
|
||||
_accessLevel = version < 2 ? (AccessLevel)reader.ReadInt() : reader.ReadEnum<AccessLevel>();
|
||||
_flags = reader.ReadInt();
|
||||
Created = reader.ReadDateTime();
|
||||
_lastLogin = reader.ReadDateTime();
|
||||
|
||||
_totalGold = reader.ReadInt();
|
||||
_totalPlat = reader.ReadInt();
|
||||
|
||||
var length = reader.ReadInt();
|
||||
_mobiles = new Mobile[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_mobiles[i] = reader.ReadEntity<Mobile>();
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_comments = length > 0 ? new List<AccountComment>(length) : null;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_comments!.Add(new AccountComment(reader));
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_tags = length > 0 ? new List<AccountTag>(length) : null;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_tags!.Add(new AccountTag(reader));
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_loginIPs = new IPAddress[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (version < 2)
|
||||
{
|
||||
if (IPAddress.TryParse(reader.ReadString(), out var address))
|
||||
{
|
||||
_loginIPs[i] = Utility.Intern(address);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_loginIPs[i] = reader.ReadIPAddress();
|
||||
}
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_ipRestrictions = new string[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_ipRestrictions[i] = reader.ReadString();
|
||||
}
|
||||
|
||||
_totalGameTime = reader.ReadTimeSpan();
|
||||
|
||||
if (version > 1)
|
||||
_email = reader.ReadString();
|
||||
|
||||
Timer.StartTimer(AfterDeserialization);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Xml;
|
||||
using Server.Accounting.Security;
|
||||
|
|
@ -11,7 +10,7 @@ using Server.Network;
|
|||
|
||||
namespace Server.Accounting
|
||||
{
|
||||
[Serializable(3)]
|
||||
[Serializable(4)]
|
||||
public partial class Account : IAccount, IComparable<Account>, ISerializable
|
||||
{
|
||||
public static readonly TimeSpan YoungDuration = TimeSpan.FromHours(40.0);
|
||||
|
|
@ -33,10 +32,7 @@ namespace Server.Accounting
|
|||
[SerializableField(4)]
|
||||
private int _flags;
|
||||
|
||||
[SerializableField(5, setter: "private")]
|
||||
private DateTime _created;
|
||||
|
||||
[SerializableField(6)]
|
||||
[SerializableField(5)]
|
||||
private DateTime _lastLogin;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -44,7 +40,7 @@ namespace Server.Accounting
|
|||
/// The value does not include the value of Platinum and ranges from
|
||||
/// 0 to 999,999,999 by default.
|
||||
/// </summary>
|
||||
[SerializableField(7, setter: "private")]
|
||||
[SerializableField(6, setter: "private")]
|
||||
[SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")]
|
||||
public int _totalGold;
|
||||
|
||||
|
|
@ -54,16 +50,16 @@ namespace Server.Accounting
|
|||
/// 0 to 2,147,483,647 by default.
|
||||
/// One Platinum represents the value of CurrencyThreshold in Gold.
|
||||
/// </summary>
|
||||
[SerializableField(8, setter: "private")]
|
||||
[SerializableField(7, setter: "private")]
|
||||
[SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")]
|
||||
public int _totalPlat;
|
||||
|
||||
[SerializableField(9, "private", "private")]
|
||||
[SerializableField(8, "private", "private")]
|
||||
private Mobile[] _mobiles;
|
||||
|
||||
private List<AccountComment> _comments;
|
||||
|
||||
[SerializableField(10)]
|
||||
[SerializableField(9)]
|
||||
public List<AccountComment> Comments
|
||||
{
|
||||
get => _comments ??= new List<AccountComment>();
|
||||
|
|
@ -76,7 +72,7 @@ namespace Server.Accounting
|
|||
|
||||
private List<AccountTag> _tags;
|
||||
|
||||
[SerializableField(11)]
|
||||
[SerializableField(10)]
|
||||
public List<AccountTag> Tags
|
||||
{
|
||||
get => _tags ??= new List<AccountTag>();
|
||||
|
|
@ -87,14 +83,14 @@ namespace Server.Accounting
|
|||
}
|
||||
}
|
||||
|
||||
[SerializableField(12)]
|
||||
[SerializableField(11)]
|
||||
private IPAddress[] _loginIPs;
|
||||
|
||||
/// <summary>
|
||||
/// List of IP addresses for restricted access. '*' wildcard supported. If the array contains zero entries, all IP addresses
|
||||
/// are allowed.
|
||||
/// </summary>
|
||||
[SerializableField(13)]
|
||||
[SerializableField(12)]
|
||||
private string[] _ipRestrictions;
|
||||
|
||||
private TimeSpan _totalGameTime;
|
||||
|
|
@ -103,7 +99,7 @@ namespace Server.Accounting
|
|||
/// Gets the total game time of this account, also considering the game time of characters
|
||||
/// that have been deleted.
|
||||
/// </summary>
|
||||
[SerializableField(14)]
|
||||
[SerializableField(13)]
|
||||
public TimeSpan TotalGameTime
|
||||
{
|
||||
get
|
||||
|
|
@ -125,7 +121,7 @@ namespace Server.Accounting
|
|||
}
|
||||
}
|
||||
|
||||
[SerializableField(15)]
|
||||
[SerializableField(14)]
|
||||
[SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")]
|
||||
private string _email;
|
||||
|
||||
|
|
@ -139,7 +135,7 @@ namespace Server.Accounting
|
|||
|
||||
_accessLevel = AccessLevel.Player;
|
||||
|
||||
_created = _lastLogin = Core.Now;
|
||||
_lastLogin = Core.Now;
|
||||
_totalGameTime = TimeSpan.Zero;
|
||||
|
||||
_mobiles = new Mobile[7];
|
||||
|
|
@ -184,7 +180,7 @@ namespace Server.Accounting
|
|||
|
||||
Enum.TryParse(Utility.GetText(node["accessLevel"], "Player"), true, out _accessLevel);
|
||||
_flags = Utility.GetXMLInt32(Utility.GetText(node["flags"], "0"), 0);
|
||||
_created = Utility.GetXMLDateTime(Utility.GetText(node["created"], null), Core.Now);
|
||||
Created = Utility.GetXMLDateTime(Utility.GetText(node["created"], null), Core.Now);
|
||||
_lastLogin = Utility.GetXMLDateTime(Utility.GetText(node["lastLogin"], null), Core.Now);
|
||||
|
||||
_totalGold = Utility.GetXMLInt32(Utility.GetText(node["totalGold"], "0"), 0);
|
||||
|
|
@ -306,10 +302,22 @@ namespace Server.Accounting
|
|||
}
|
||||
}
|
||||
|
||||
public TimeSpan AccountAge => Core.Now - Created;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, readOnly: true)]
|
||||
public DateTime Created { get; set; } = Core.Now;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
DateTime ISerializable.LastSerialized { get; set; } = Core.Now;
|
||||
|
||||
public int TypeRef { get; private set; }
|
||||
|
||||
public Serial Serial { get; set; }
|
||||
|
||||
public void BeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
[AfterDeserialization]
|
||||
private void AfterDeserialization()
|
||||
{
|
||||
|
|
@ -348,77 +356,6 @@ namespace Server.Accounting
|
|||
}
|
||||
}
|
||||
|
||||
private void Deserialize(IGenericReader reader, int version)
|
||||
{
|
||||
if (version != 2)
|
||||
{
|
||||
// Due to a bug where we were not versioning at all, reset so we don't have an issue deserializing
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
_username = reader.ReadString();
|
||||
_passwordAlgorithm = version < 2 ? (PasswordProtectionAlgorithm)reader.ReadInt() : reader.ReadEnum<PasswordProtectionAlgorithm>();
|
||||
_password = reader.ReadString();
|
||||
_accessLevel = version < 2 ? (AccessLevel)reader.ReadInt() : reader.ReadEnum<AccessLevel>();
|
||||
_flags = reader.ReadInt();
|
||||
_created = reader.ReadDateTime();
|
||||
_lastLogin = reader.ReadDateTime();
|
||||
|
||||
_totalGold = reader.ReadInt();
|
||||
_totalPlat = reader.ReadInt();
|
||||
|
||||
var length = reader.ReadInt();
|
||||
_mobiles = new Mobile[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_mobiles[i] = reader.ReadEntity<Mobile>();
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_comments = length > 0 ? new List<AccountComment>(length) : null;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_comments!.Add(new AccountComment(reader));
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_tags = length > 0 ? new List<AccountTag>(length) : null;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_tags!.Add(new AccountTag(reader));
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_loginIPs = new IPAddress[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (version < 2)
|
||||
{
|
||||
if (IPAddress.TryParse(reader.ReadString(), out var address))
|
||||
{
|
||||
_loginIPs[i] = Utility.Intern(address);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_loginIPs[i] = reader.ReadIPAddress();
|
||||
}
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_ipRestrictions = new string[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_ipRestrictions[i] = reader.ReadString();
|
||||
}
|
||||
|
||||
_totalGameTime = reader.ReadTimeSpan();
|
||||
|
||||
_email = reader.ReadString();
|
||||
|
||||
Timer.StartTimer(AfterDeserialization);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the account, all characters of the account, and all houses of those characters
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ namespace Server.Misc
|
|||
{
|
||||
res = DeleteResultType.CharBeingPlayed;
|
||||
}
|
||||
else if (RestrictDeletion && Core.Now < m.CreationTime + DeleteDelay)
|
||||
else if (RestrictDeletion && Core.Now < m.Created + DeleteDelay)
|
||||
{
|
||||
res = DeleteResultType.CharTooYoung;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,67 +76,57 @@ namespace Server.Commands
|
|||
CommandLogging.Enabled = false;
|
||||
}
|
||||
|
||||
List<object> usedList;
|
||||
var propertyChains = new Dictionary<Type, PropertyInfo[]>();
|
||||
var usedList = new List<object>(list.Count);
|
||||
|
||||
if (Utility.InsensitiveCompare(bc.Object, "Current") == 0)
|
||||
for (var j = 0; j < list.Count; ++j)
|
||||
{
|
||||
usedList = list;
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertyChains = new Dictionary<Type, PropertyInfo[]>();
|
||||
var obj = list[j];
|
||||
|
||||
usedList = new List<object>(list.Count);
|
||||
|
||||
for (var j = 0; j < list.Count; ++j)
|
||||
if (obj == null)
|
||||
{
|
||||
var obj = list[j];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (obj == null)
|
||||
var type = obj.GetType();
|
||||
var failReason = "";
|
||||
|
||||
if (!propertyChains.TryGetValue(type, out var chain))
|
||||
{
|
||||
propertyChains[type] = chain = Properties.GetPropertyInfoChain(
|
||||
e.Mobile,
|
||||
type,
|
||||
bc.Object,
|
||||
PropertyAccess.Read,
|
||||
out failReason
|
||||
);
|
||||
}
|
||||
|
||||
if (chain == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var endProp = Properties.GetPropertyInfo(ref obj, chain, out failReason);
|
||||
|
||||
if (endProp == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
obj = endProp.GetValue(obj, null);
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = obj.GetType();
|
||||
var failReason = "";
|
||||
|
||||
if (!propertyChains.TryGetValue(type, out var chain))
|
||||
{
|
||||
propertyChains[type] = chain = Properties.GetPropertyInfoChain(
|
||||
e.Mobile,
|
||||
type,
|
||||
bc.Object,
|
||||
PropertyAccess.Read,
|
||||
ref failReason
|
||||
);
|
||||
}
|
||||
|
||||
if (chain == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var endProp = Properties.GetPropertyInfo(ref obj, chain, ref failReason);
|
||||
|
||||
if (endProp == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
obj = endProp.GetValue(obj, null);
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
usedList.Add(obj);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
usedList.Add(obj);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
command.ExecuteList(eventArgs[i], usedList);
|
||||
|
|
|
|||
|
|
@ -115,13 +115,12 @@ namespace Server.Commands.Generic
|
|||
|
||||
if (obj != null)
|
||||
{
|
||||
string failReason = null;
|
||||
var chain = Properties.GetPropertyInfoChain(
|
||||
m_From,
|
||||
obj.GetType(),
|
||||
m_Columns[i],
|
||||
PropertyAccess.Read,
|
||||
ref failReason
|
||||
out _
|
||||
);
|
||||
|
||||
if (chain?.Length > 0)
|
||||
|
|
@ -187,14 +186,13 @@ namespace Server.Commands.Generic
|
|||
var src = obj;
|
||||
|
||||
string value;
|
||||
var failReason = "";
|
||||
|
||||
var chain = Properties.GetPropertyInfoChain(
|
||||
m_From,
|
||||
src.GetType(),
|
||||
m_Columns[j],
|
||||
PropertyAccess.Read,
|
||||
ref failReason
|
||||
out _
|
||||
);
|
||||
|
||||
if (chain == null || chain.Length == 0)
|
||||
|
|
@ -203,7 +201,7 @@ namespace Server.Commands.Generic
|
|||
}
|
||||
else
|
||||
{
|
||||
var p = Properties.GetPropertyInfo(ref src, chain, ref failReason);
|
||||
var p = Properties.GetPropertyInfo(ref src, chain, out _);
|
||||
|
||||
if (p == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Server.Misc;
|
|||
using Server.Mobiles;
|
||||
using Server.Multis;
|
||||
using Server.Network;
|
||||
using Server.Saves;
|
||||
using Server.Spells;
|
||||
using Server.Targeting;
|
||||
using Server.Targets;
|
||||
|
|
@ -44,8 +45,6 @@ namespace Server.Commands
|
|||
|
||||
Register("Help", AccessLevel.Player, Help_OnCommand);
|
||||
|
||||
Register("Save", AccessLevel.Administrator, Save_OnCommand);
|
||||
|
||||
Register("Move", AccessLevel.GameMaster, Move_OnCommand);
|
||||
Register("Client", AccessLevel.Counselor, Client_OnCommand);
|
||||
|
||||
|
|
@ -459,12 +458,6 @@ namespace Server.Commands
|
|||
e.Mobile.Target = new PickMoveTarget();
|
||||
}
|
||||
|
||||
[Usage("Save"), Description("Saves the world.")]
|
||||
private static void Save_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
AutoSave.Save();
|
||||
}
|
||||
|
||||
private static bool FixMap(ref Map map, ref Point3D loc, Item item) =>
|
||||
map != null && map != Map.Internal || item.RootParent is Mobile m &&
|
||||
FixMap(ref map, ref loc, m);
|
||||
|
|
|
|||
|
|
@ -149,46 +149,16 @@ namespace Server.Commands
|
|||
|
||||
for (var i = 0; i < realProps.Length; ++i)
|
||||
{
|
||||
PropertyInfo thisProp = null;
|
||||
|
||||
var propName = props[i, 0];
|
||||
var thisProp = Properties.GetPropertyInfoByName(from, allProps, propName, PropertyAccess.Write, out var failReason);
|
||||
|
||||
for (var j = 0; thisProp == null && j < allProps.Length; ++j)
|
||||
if (failReason == null)
|
||||
{
|
||||
if (propName.InsensitiveEquals(allProps[j].Name))
|
||||
{
|
||||
thisProp = allProps[j];
|
||||
}
|
||||
}
|
||||
|
||||
if (thisProp == null)
|
||||
{
|
||||
from.SendMessage("Property not found: {0}", propName);
|
||||
realProps[i] = thisProp;
|
||||
}
|
||||
else
|
||||
{
|
||||
var attr = GetCPA(thisProp);
|
||||
|
||||
if (attr == null)
|
||||
{
|
||||
from.SendMessage("Property ({0}) not found.", propName);
|
||||
}
|
||||
else if (from.AccessLevel < attr.WriteLevel)
|
||||
{
|
||||
from.SendMessage(
|
||||
"Setting this property ({0}) requires at least {1} access level.",
|
||||
propName,
|
||||
Mobile.GetAccessLevelName(attr.WriteLevel)
|
||||
);
|
||||
}
|
||||
else if (!thisProp.CanWrite || attr.ReadOnly)
|
||||
{
|
||||
from.SendMessage("Property ({0}) is read only.", propName);
|
||||
}
|
||||
else
|
||||
{
|
||||
realProps[i] = thisProp;
|
||||
}
|
||||
from.SendMessage(failReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1078,7 +1078,7 @@ namespace Server.Commands
|
|||
}
|
||||
}
|
||||
}
|
||||
else if ((TileData.ItemTable[itemID & TileData.MaxItemValue].Flags & TileFlag.LightSource) != 0)
|
||||
else if (TileData.ItemTable[itemID & TileData.MaxItemValue].LightSource)
|
||||
{
|
||||
eable = map.GetItemsInRange(new Point3D(x, y, z), 0);
|
||||
|
||||
|
|
@ -1100,7 +1100,7 @@ namespace Server.Commands
|
|||
res = true;
|
||||
}
|
||||
}
|
||||
else if ((item.ItemData.Flags & TileFlag.LightSource) != 0 && item.ItemData.Name == srcName)
|
||||
else if (item.ItemData.LightSource && item.ItemData.Name == srcName)
|
||||
{
|
||||
m_DeleteQueue.Enqueue(item);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1074,7 +1074,7 @@ namespace Server.Commands
|
|||
}
|
||||
}
|
||||
}
|
||||
else if ((TileData.ItemTable[itemID & TileData.MaxItemValue].Flags & TileFlag.LightSource) != 0)
|
||||
else if (TileData.ItemTable[itemID & TileData.MaxItemValue].LightSource)
|
||||
{
|
||||
eable = map.GetItemsInRange(new Point3D(x, y, z), 0);
|
||||
|
||||
|
|
@ -1096,7 +1096,7 @@ namespace Server.Commands
|
|||
res = true;
|
||||
}
|
||||
}
|
||||
else if ((item.ItemData.Flags & TileFlag.LightSource) != 0 && item.ItemData.Name == srcName)
|
||||
else if (item.ItemData.LightSource && item.ItemData.Name == srcName)
|
||||
{
|
||||
m_DeleteQueue.Enqueue(item);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,13 +55,69 @@ namespace Server.Commands
|
|||
}
|
||||
}
|
||||
|
||||
private static bool CIEqual(string l, string r) => l.InsensitiveEquals(r);
|
||||
public static PropertyInfo GetPropertyInfoByName(
|
||||
Mobile from, PropertyInfo[] props, string propertyName, PropertyAccess access, out string failReason
|
||||
)
|
||||
{
|
||||
for (var i = 0; i < props.Length; i++)
|
||||
{
|
||||
var p = props[i];
|
||||
|
||||
if (!p.Name.InsensitiveEquals(propertyName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var attr = GetCPA(p);
|
||||
|
||||
if (attr == null)
|
||||
{
|
||||
failReason = $"Property '${propertyName}' not found.";
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Read) != 0 && from.AccessLevel < attr.ReadLevel)
|
||||
{
|
||||
failReason =
|
||||
$"You must be at least {Mobile.GetAccessLevelName(attr.ReadLevel)} to get the property '{propertyName}'.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Write) != 0 && from.AccessLevel < attr.WriteLevel)
|
||||
{
|
||||
failReason =
|
||||
$"You must be at least {Mobile.GetAccessLevelName(attr.WriteLevel)} to set the property '{propertyName}'.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Read) != 0 && !p.CanRead)
|
||||
{
|
||||
failReason = $"Property '{propertyName}' is write only.";
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Write) != 0 && (!p.CanWrite && !attr.CanModify || attr.ReadOnly))
|
||||
{
|
||||
failReason = $"Property '{propertyName}' is read only.";
|
||||
return null;
|
||||
}
|
||||
|
||||
failReason = null;
|
||||
return p;
|
||||
}
|
||||
|
||||
failReason = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static PropertyInfo[] GetPropertyInfoChain(
|
||||
Mobile from, Type type, string propertyString,
|
||||
PropertyAccess endAccess, ref string failReason
|
||||
PropertyAccess access, out string failReason
|
||||
)
|
||||
{
|
||||
failReason = null;
|
||||
var split = propertyString.Split('.');
|
||||
|
||||
if (split.Length == 0)
|
||||
|
|
@ -74,76 +130,18 @@ namespace Server.Commands
|
|||
for (var i = 0; i < info.Length; ++i)
|
||||
{
|
||||
var propertyName = split[i];
|
||||
|
||||
if (CIEqual(propertyName, "current"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var props = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
var isFinal = i == info.Length - 1;
|
||||
var p = GetPropertyInfoByName(from, props, propertyName, access, out failReason);
|
||||
|
||||
var access = endAccess;
|
||||
|
||||
if (!isFinal)
|
||||
if (p == null)
|
||||
{
|
||||
access |= PropertyAccess.Read;
|
||||
}
|
||||
|
||||
for (var j = 0; j < props.Length; ++j)
|
||||
{
|
||||
var p = props[j];
|
||||
|
||||
if (CIEqual(p.Name, propertyName))
|
||||
{
|
||||
var attr = GetCPA(p);
|
||||
|
||||
if (attr == null)
|
||||
{
|
||||
failReason = $"Property '{propertyName}' not found.";
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Read) != 0 && from.AccessLevel < attr.ReadLevel)
|
||||
{
|
||||
failReason =
|
||||
$"You must be at least {Mobile.GetAccessLevelName(attr.ReadLevel)} to get the property '{propertyName}'.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Write) != 0 && from.AccessLevel < attr.WriteLevel)
|
||||
{
|
||||
failReason =
|
||||
$"You must be at least {Mobile.GetAccessLevelName(attr.WriteLevel)} to set the property '{propertyName}'.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Read) != 0 && !p.CanRead)
|
||||
{
|
||||
failReason = $"Property '{propertyName}' is write only.";
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Write) != 0 && (!p.CanWrite || attr.ReadOnly) && isFinal)
|
||||
{
|
||||
failReason = $"Property '{propertyName}' is read only.";
|
||||
return null;
|
||||
}
|
||||
|
||||
info[i] = p;
|
||||
type = p.PropertyType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (info[i] == null)
|
||||
{
|
||||
failReason = $"Property '{propertyName}' not found.";
|
||||
failReason ??= $"Property '{propertyName}' not found.";
|
||||
return null;
|
||||
}
|
||||
|
||||
info[i] = p;
|
||||
type = p.PropertyType;
|
||||
}
|
||||
|
||||
return info;
|
||||
|
|
@ -151,15 +149,15 @@ namespace Server.Commands
|
|||
|
||||
public static PropertyInfo GetPropertyInfo(
|
||||
Mobile from, ref object obj, string propertyName, PropertyAccess access,
|
||||
ref string failReason
|
||||
out string failReason
|
||||
)
|
||||
{
|
||||
var chain = GetPropertyInfoChain(from, obj.GetType(), propertyName, access, ref failReason);
|
||||
var chain = GetPropertyInfoChain(from, obj.GetType(), propertyName, access, out failReason);
|
||||
|
||||
return chain == null ? null : GetPropertyInfo(ref obj, chain, ref failReason);
|
||||
return chain == null ? null : GetPropertyInfo(ref obj, chain, out failReason);
|
||||
}
|
||||
|
||||
public static PropertyInfo GetPropertyInfo(ref object obj, PropertyInfo[] chain, ref string failReason)
|
||||
public static PropertyInfo GetPropertyInfo(ref object obj, PropertyInfo[] chain, out string failReason)
|
||||
{
|
||||
if (chain == null || chain.Length == 0)
|
||||
{
|
||||
|
|
@ -183,6 +181,7 @@ namespace Server.Commands
|
|||
}
|
||||
}
|
||||
|
||||
failReason = null;
|
||||
return chain[^1];
|
||||
}
|
||||
|
||||
|
|
@ -190,14 +189,14 @@ namespace Server.Commands
|
|||
{
|
||||
var failReason = "";
|
||||
|
||||
var chain = GetPropertyInfoChain(from, o.GetType(), name, PropertyAccess.Read, ref failReason);
|
||||
var chain = GetPropertyInfoChain(from, o.GetType(), name, PropertyAccess.Read, out failReason);
|
||||
|
||||
if (chain == null || chain.Length == 0)
|
||||
{
|
||||
return failReason;
|
||||
}
|
||||
|
||||
var p = GetPropertyInfo(ref o, chain, ref failReason);
|
||||
var p = GetPropertyInfo(ref o, chain, out failReason);
|
||||
|
||||
return p == null ? failReason : InternalGetValue(o, p, chain);
|
||||
}
|
||||
|
|
@ -246,9 +245,8 @@ namespace Server.Commands
|
|||
return "Zero is not a valid value to offset.";
|
||||
}
|
||||
|
||||
string failReason = null;
|
||||
realObjs[i] = o;
|
||||
realProps[i] = GetPropertyInfo(from, ref realObjs[i], name, PropertyAccess.ReadWrite, ref failReason);
|
||||
realProps[i] = GetPropertyInfo(from, ref realObjs[i], name, PropertyAccess.ReadWrite, out var failReason);
|
||||
|
||||
if (failReason != null)
|
||||
{
|
||||
|
|
@ -353,8 +351,7 @@ namespace Server.Commands
|
|||
{
|
||||
var logObject = o;
|
||||
|
||||
var failReason = "";
|
||||
var p = GetPropertyInfo(from, ref o, name, PropertyAccess.Write, ref failReason);
|
||||
var p = GetPropertyInfo(from, ref o, name, PropertyAccess.Write, out var failReason);
|
||||
|
||||
return p == null ? failReason : InternalSetValue(from, logObject, o, p, name, value, true);
|
||||
}
|
||||
|
|
@ -407,7 +404,6 @@ namespace Server.Commands
|
|||
TryParse(p.PropertyType, value, out var toSet) ??
|
||||
SetDirect(from, logobj, o, p, pname, toSet, shouldLog);
|
||||
|
||||
|
||||
private class PropsTarget : Target
|
||||
{
|
||||
public PropsTarget() : base(-1, true, TargetFlags.None)
|
||||
|
|
|
|||
110
Projects/UOContent/Compression/TarArchive.cs
Executable file
110
Projects/UOContent/Compression/TarArchive.cs
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using Server.Buffers;
|
||||
|
||||
namespace Server.Compression
|
||||
{
|
||||
public static class TarArchive
|
||||
{
|
||||
private const string _libArchiveWindowsUrl = @"https://www.libarchive.org/downloads/libarchive-v3.5.2-win64.zip";
|
||||
private static string _pathToTar;
|
||||
|
||||
private static string GetPathToTar()
|
||||
{
|
||||
if (!Core.IsWindows || File.Exists(@"C:\Windows\system32\tar.exe"))
|
||||
{
|
||||
return "tar";
|
||||
}
|
||||
|
||||
var tarPath = Path.Combine(Core.BaseDirectory, "bsdtar/bsdtar.exe");
|
||||
return File.Exists(tarPath) ? tarPath : DownloadTarForWindows();
|
||||
}
|
||||
|
||||
private static int RunTar(string arguments, string compressionProgramPath)
|
||||
{
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = _pathToTar,
|
||||
Arguments = arguments
|
||||
}
|
||||
};
|
||||
|
||||
if (compressionProgramPath != null)
|
||||
{
|
||||
var envPaths = $"{process.StartInfo.EnvironmentVariables["PATH"]}:{compressionProgramPath}";
|
||||
process.StartInfo.EnvironmentVariables["PATH"] = envPaths;
|
||||
}
|
||||
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
||||
return process.ExitCode;
|
||||
}
|
||||
|
||||
private static string DownloadTarForWindows()
|
||||
{
|
||||
var tempDir = PathUtility.EnsureRandomPath(Path.GetTempPath());
|
||||
|
||||
var libarchiveFile = Path.Combine(tempDir, "libarchive.zip");
|
||||
using WebClient wc = new WebClient();
|
||||
wc.DownloadFile (new Uri(_libArchiveWindowsUrl), libarchiveFile);
|
||||
|
||||
ZipFile.ExtractToDirectory(libarchiveFile, tempDir);
|
||||
var libArchivePath = Path.Combine(tempDir, "libarchive");
|
||||
Directory.Move(Path.Combine(libArchivePath, "bin"), "bsdtar");
|
||||
Directory.Delete(libArchivePath, true);
|
||||
File.Delete(libarchiveFile);
|
||||
|
||||
return Path.Combine(Core.BaseDirectory, "bsdtar/bsdtar.exe");
|
||||
}
|
||||
|
||||
public static bool ExtractToDirectory(
|
||||
string fileNamePath,
|
||||
string outputDirectory,
|
||||
string compressCommand = null,
|
||||
string compressionProgramPath = null
|
||||
)
|
||||
{
|
||||
_pathToTar ??= GetPathToTar();
|
||||
|
||||
var useExternalCompression = compressCommand != null ? $"--use-compress-program \"{compressCommand}\" " : "";
|
||||
var arguments = $"{useExternalCompression} -xf \"{fileNamePath}\" -C \"{outputDirectory}\"";
|
||||
|
||||
return RunTar(arguments, compressionProgramPath) == 0;
|
||||
}
|
||||
|
||||
public static bool CreateFromPaths(
|
||||
IEnumerable<string> paths,
|
||||
string destinationArchiveFileName,
|
||||
string relativeTo,
|
||||
string compressCommand = null,
|
||||
string compressionProgramPath = null
|
||||
)
|
||||
{
|
||||
_pathToTar ??= GetPathToTar();
|
||||
|
||||
new FileInfo(destinationArchiveFileName).EnsureDirectory();
|
||||
|
||||
using var builder = new ValueStringBuilder();
|
||||
var i = 0;
|
||||
foreach (var path in paths)
|
||||
{
|
||||
builder.Append($"{(i++ > 0 ? " " : "")}\"{Path.GetRelativePath(relativeTo, path)}\"");
|
||||
}
|
||||
var pathsToCompress = builder.ToString();
|
||||
|
||||
var tarFlags = compressCommand == null ? "-acf" : "-cf";
|
||||
var useExternalCompression = compressCommand != null ? $"--use-compress-program \"{compressCommand}\" " : "";
|
||||
var arguments = $"{useExternalCompression}{tarFlags} \"{destinationArchiveFileName}\" -C \"{relativeTo}\" {pathsToCompress}";
|
||||
|
||||
return RunTar(arguments, compressionProgramPath) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
Projects/UOContent/Compression/ZstdArchive.cs
Executable file
95
Projects/UOContent/Compression/ZstdArchive.cs
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Server.Compression
|
||||
{
|
||||
public static class ZstdArchive
|
||||
{
|
||||
private static readonly string _pathToZstd = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory?.FullName;
|
||||
|
||||
public static bool ExtractToDirectory(string fileNamePath, string outputDirectory)
|
||||
{
|
||||
// bsdtar has a bug and hangs, so we are doing it in two steps.
|
||||
if (Core.IsWindows)
|
||||
{
|
||||
var tempDir = PathUtility.EnsureRandomPath(Path.GetTempPath());
|
||||
var tempTarArchive = Path.Combine(tempDir, "temp-file.tar");
|
||||
|
||||
try
|
||||
{
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path.Combine(_pathToZstd, "zstd.exe"),
|
||||
Arguments = $"-q -d \"{fileNamePath}\" -o \"{tempTarArchive}\""
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
||||
return process.ExitCode == 0 && TarArchive.ExtractToDirectory(tempTarArchive, outputDirectory);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempTarArchive);
|
||||
}
|
||||
}
|
||||
|
||||
return TarArchive.ExtractToDirectory(fileNamePath, outputDirectory, "zstd -q -d", _pathToZstd);
|
||||
}
|
||||
|
||||
public static bool CreateFromPaths(
|
||||
IEnumerable<string> paths,
|
||||
string destinationArchiveFileName,
|
||||
string relativeTo
|
||||
)
|
||||
{
|
||||
// bsdtar has a bug and hangs, so we are doing it in two steps.
|
||||
if (Core.IsWindows)
|
||||
{
|
||||
var tempDir = PathUtility.EnsureRandomPath(Path.GetTempPath());
|
||||
var tempTarArchive = Path.Combine(tempDir, "temp-file.tar");
|
||||
|
||||
try
|
||||
{
|
||||
if (!TarArchive.CreateFromPaths(paths, tempTarArchive, relativeTo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path.Combine(_pathToZstd, "zstd.exe"),
|
||||
Arguments = $"-q \"{tempTarArchive}\" -o \"{destinationArchiveFileName}\""
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
||||
return process.ExitCode == 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
}
|
||||
|
||||
return TarArchive.CreateFromPaths(paths, destinationArchiveFileName, relativeTo, "zstd -q", _pathToZstd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ namespace Server.Engines.BulkOrders
|
|||
[SerializableFieldAttr("[CommandProperty(AccessLevel.GameMaster)]")]
|
||||
private bool _requireExceptional;
|
||||
|
||||
[SerializableField(1)]
|
||||
[SerializableField(2)]
|
||||
[InvalidateProperties]
|
||||
[SerializableFieldAttr("[CommandProperty(AccessLevel.GameMaster)]")]
|
||||
private BulkMaterialType _material;
|
||||
|
|
|
|||
|
|
@ -410,7 +410,6 @@ namespace Server.Engines.CannedEvil
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Justice reward
|
||||
var pm = (PlayerMobile)killer;
|
||||
for (var j = 0; j < pm.JusticeProtectors.Count; ++j)
|
||||
|
|
|
|||
|
|
@ -54,10 +54,8 @@ namespace Server.Engines.Craft
|
|||
{
|
||||
var type = item.GetType();
|
||||
|
||||
var isAnvil = type.IsDefined(typeofAnvil, false) || item.ItemID == 4015 || item.ItemID == 4016 ||
|
||||
item.ItemID == 0x2DD5 || item.ItemID == 0x2DD6;
|
||||
var isForge = type.IsDefined(typeofForge, false) || item.ItemID == 4017 ||
|
||||
item.ItemID >= 6522 && item.ItemID <= 6569 || item.ItemID == 0x2DD8;
|
||||
var isAnvil = type.IsDefined(typeofAnvil, false) || item.ItemID is 4015 or 4016 or 11733 or 11734;
|
||||
var isForge = type.IsDefined(typeofForge, false) || item.ItemID is 4017 or >= 6522 and <= 6569 or 11736;
|
||||
|
||||
if (isAnvil || isForge)
|
||||
{
|
||||
|
|
@ -88,8 +86,8 @@ namespace Server.Engines.Craft
|
|||
{
|
||||
var id = tiles[i].ID;
|
||||
|
||||
var isAnvil = id == 4015 || id == 4016 || id == 0x2DD5 || id == 0x2DD6;
|
||||
var isForge = id == 4017 || id >= 6522 && id <= 6569 || id == 0x2DD8;
|
||||
var isAnvil = id is 4015 or 4016 or 11733 or 11734;
|
||||
var isForge = id is 4017 or >= 6522 and <= 6569 or 11736;
|
||||
|
||||
if (isAnvil || isForge)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -104,26 +104,13 @@ namespace Server.Engines.Craft
|
|||
index = AddCraft(typeof(RedHangingLantern), 1044294, 1029412, 65.0, 90.0, typeof(Log), 1044041, 5, 1044351);
|
||||
AddRes(index, typeof(BlankScroll), 1044377, 10, 1044378);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(WhiteHangingLantern),
|
||||
1044294,
|
||||
1029416,
|
||||
65.0,
|
||||
90.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
5,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(WhiteHangingLantern), 1044294, 1029416, 65.0, 90.0, typeof(Log), 1044041, 5, 1044351);
|
||||
AddRes(index, typeof(BlankScroll), 1044377, 10, 1044378);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(typeof(ShojiScreen), 1044294, 1029423, 80.0, 105.0, typeof(Log), 1044041, 75, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 50.0, 55.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 60, 1044287);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(typeof(BambooScreen), 1044294, 1029428, 80.0, 105.0, typeof(Log), 1044041, 75, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 50.0, 55.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 60, 1044287);
|
||||
|
|
@ -132,17 +119,7 @@ namespace Server.Engines.Craft
|
|||
|
||||
if (Core.AOS) // Duplicate Entries to preserve ordering depending on era
|
||||
{
|
||||
index = AddCraft(
|
||||
typeof(FishingPole),
|
||||
1044294,
|
||||
1023519,
|
||||
68.4,
|
||||
93.4,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
5,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(FishingPole), 1044294, 1023519, 68.4, 93.4, typeof(Log), 1044041, 5, 1044351 );
|
||||
AddSkill(index, SkillName.Tailoring, 40.0, 45.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 5, 1044287);
|
||||
}
|
||||
|
|
@ -154,78 +131,35 @@ namespace Server.Engines.Craft
|
|||
AddRes(index, typeof(RunedPrism), 1073465, 1, 1053098);
|
||||
AddRes(index, typeof(JeweledFiligree), 1072894, 1, 1053098);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(WarriorStatueSouthDeed),
|
||||
1044294,
|
||||
1072887,
|
||||
0.0,
|
||||
35.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
250,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(ArcanistStatueSouthDeed), 1044294, 1072885, 0.0, 35.0, typeof(Log), 1044041, 250, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ArcanistStatueEastDeed), 1044294, 1072886, 0.0, 35.0, typeof(Log), 1044041, 250, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(WarriorStatueSouthDeed), 1044294, 1072887, 0.0, 35.0, typeof(Log), 1044041, 250, 1044351);
|
||||
AddRecipe(index, 300);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(WarriorStatueEastDeed),
|
||||
1044294,
|
||||
1072888,
|
||||
0.0,
|
||||
35.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
250,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(WarriorStatueEastDeed), 1044294, 1072888, 0.0, 35.0, typeof(Log), 1044041, 250, 1044351);
|
||||
AddRecipe(index, 301);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(SquirrelStatueSouthDeed),
|
||||
1044294,
|
||||
1072884,
|
||||
0.0,
|
||||
35.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
250,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(SquirrelStatueSouthDeed), 1044294, 1072884, 0.0, 35.0, typeof(Log), 1044041, 250, 1044351);
|
||||
AddRecipe(index, 302);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(SquirrelStatueEastDeed),
|
||||
1044294,
|
||||
1073398,
|
||||
0.0,
|
||||
35.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
250,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(SquirrelStatueEastDeed), 1044294, 1073398, 0.0, 35.0, typeof(Log), 1044041, 250, 1044351);
|
||||
AddRecipe(index, 303);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(AcidProofRope),
|
||||
1044294,
|
||||
1074886,
|
||||
80,
|
||||
130.0,
|
||||
typeof(GreaterStrengthPotion),
|
||||
1073466,
|
||||
2,
|
||||
1044253
|
||||
);
|
||||
index = AddCraft(typeof(GiantReplicaAcorn), 1044294, 1072889, 80.0, 105.0, typeof(Log), 1044041, 35, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(MountedDreadHorn), 1044294, 1032632, 90.0, 115.0, typeof(Log), 1044041, 50, 1044351);
|
||||
AddRes(index, typeof(PristineDreadHorn), 1032634, 1, 1053098);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(AcidProofRope), 1044294, 1074886, 80, 130.0, typeof(GreaterStrengthPotion), 1073466, 2, 1044253);
|
||||
AddRes(index, typeof(ProtectionScroll), 1044395, 1, 1053098);
|
||||
AddRes(index, typeof(SwitchItem), 1032127, 1, 1053098);
|
||||
AddRareRecipe(index, 304);
|
||||
|
|
@ -252,16 +186,29 @@ namespace Server.Engines.Craft
|
|||
{
|
||||
index = AddCraft(typeof(ElegantLowTable), 1044291, 1030265, 80.0, 105.0, typeof(Log), 1044041, 35, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(typeof(PlainLowTable), 1044291, 1030266, 80.0, 105.0, typeof(Log), 1044041, 35, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
}
|
||||
|
||||
if (Core.ML)
|
||||
{
|
||||
index = AddCraft(typeof(OrnateElvenTableSouthDeed), 1044291, 1072869, 85.0, 110.0, typeof(Log), 1044041, 60, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(OrnateElvenTableEastDeed), 1044291, 1073384, 85.0, 110.0, typeof(Log), 1044041, 60, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(FancyElvenTableSouthDeed), 1044291, 1073385, 80.0, 105.0, typeof(Log), 1044041, 50, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(FancyElvenTableEastDeed), 1044291, 1073386, 80.0, 105.0, typeof(Log), 1044041, 50, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenPodium), 1044291, 1073399, 80.0, 105.0, typeof(Log), 1044041, 20, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(OrnateElvenChair), 1044291, 1072870, 80.0, 105.0, typeof(Log), 1044041, 30, 1044351);
|
||||
AddRecipe(index, 305);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(BigElvenChair), 1044291, 1072872, 85.0, 110.0, typeof(Log), 1044041, 40, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenReadingChair), 1044291, 1072873, 80.0, 105.0, typeof(Log), 1044041, 30, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
}
|
||||
|
||||
// Containers
|
||||
|
|
@ -278,64 +225,24 @@ namespace Server.Engines.Craft
|
|||
{
|
||||
index = AddCraft(typeof(PlainWoodenChest), 1044292, 1030251, 90.0, 115.0, typeof(Log), 1044041, 30, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(OrnateWoodenChest),
|
||||
1044292,
|
||||
1030253,
|
||||
90.0,
|
||||
115.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
30,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(OrnateWoodenChest), 1044292, 1030253, 90.0, 115.0, typeof(Log), 1044041, 30, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(GildedWoodenChest),
|
||||
1044292,
|
||||
1030255,
|
||||
90.0,
|
||||
115.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
30,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(GildedWoodenChest), 1044292, 1030255, 90.0, 115.0, typeof(Log), 1044041, 30, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(typeof(WoodenFootLocker), 1044292, 1030257, 90.0, 115.0, typeof(Log), 1044041, 30, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(FinishedWoodenChest),
|
||||
1044292,
|
||||
1030259,
|
||||
90.0,
|
||||
115.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
30,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(FinishedWoodenChest), 1044292, 1030259, 90.0, 115.0, typeof(Log), 1044041, 30, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(typeof(TallCabinet), 1044292, 1030261, 90.0, 115.0, typeof(Log), 1044041, 35, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(typeof(ShortCabinet), 1044292, 1030263, 90.0, 115.0, typeof(Log), 1044041, 35, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(typeof(RedArmoire), 1044292, 1030328, 90.0, 115.0, typeof(Log), 1044041, 40, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(typeof(ElegantArmoire), 1044292, 1030330, 90.0, 115.0, typeof(Log), 1044041, 40, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(typeof(MapleArmoire), 1044292, 1030332, 90.0, 115.0, typeof(Log), 1044041, 40, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(typeof(CherryArmoire), 1044292, 1030334, 90.0, 115.0, typeof(Log), 1044041, 40, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
}
|
||||
|
|
@ -346,193 +253,77 @@ namespace Server.Engines.Craft
|
|||
|
||||
if (Core.ML)
|
||||
{
|
||||
index = AddCraft(
|
||||
typeof(ArcaneBookshelfSouthDeed),
|
||||
1044292,
|
||||
1072871,
|
||||
94.7,
|
||||
119.7,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
80,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(ArcaneBookshelfSouthDeed), 1044292, 1072871, 94.7, 119.7, typeof(Log), 1044041, 80, 1044351);
|
||||
AddRecipe(index, 306);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(ArcaneBookshelfEastDeed),
|
||||
1044292,
|
||||
1073371,
|
||||
94.7,
|
||||
119.7,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
80,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(ArcaneBookshelfEastDeed), 1044292, 1073371, 94.7, 119.7, typeof(Log), 1044041, 80, 1044351);
|
||||
AddRecipe(index, 307);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
/* TODO
|
||||
index = AddCraft( typeof( OrnateElvenChestSouthDeed ), 1044292, 1072862, 94.7, 119.7, typeof( Log ), 1044041, 40, 1044351 );
|
||||
AddRecipe( index, 308 );
|
||||
ForceNonExceptional( index );
|
||||
SetNeededExpansion( index, Expansion.ML );
|
||||
|
||||
index = AddCraft( typeof( OrnateElvenChestEastDeed ), 1044292, 1073383, 94.7, 119.7, typeof( Log ), 1044041, 40, 1044351 );
|
||||
AddRecipe( index, 309 );
|
||||
ForceNonExceptional( index );
|
||||
SetNeededExpansion( index, Expansion.ML );
|
||||
*/
|
||||
|
||||
index = AddCraft(
|
||||
typeof(ElvenDresserSouthDeed),
|
||||
1044292,
|
||||
1072864,
|
||||
75.0,
|
||||
100.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
45,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft( typeof(OrnateElvenChestSouthDeed), 1044292, 1072862, 94.7, 119.7, typeof(Log), 1044041, 40, 1044351);
|
||||
AddRecipe(index, 308);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft( typeof(OrnateElvenChestEastDeed), 1044292, 1073383, 94.7, 119.7, typeof(Log), 1044041, 40, 1044351);
|
||||
AddRecipe(index, 309);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenWashBasinSouthDeed), 1044292, 1072865, 70.0, 95.0, typeof(Log), 1044041, 40, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenWashBasinEastDeed), 1044292, 1072865, 70.0, 95.0, typeof(Log), 1044041, 40, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenDresserSouthDeed), 1044292, 1072864, 75.0, 100.0, typeof(Log), 1044041, 45, 1044351);
|
||||
AddRecipe(index, 310);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(ElvenDresserEastDeed),
|
||||
1044292,
|
||||
1073388,
|
||||
75.0,
|
||||
100.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
45,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(ElvenDresserEastDeed), 1044292, 1073388, 75.0, 100.0, typeof(Log), 1044041, 45, 1044351);
|
||||
AddRecipe(index, 311);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
/* TODO
|
||||
index = AddCraft( typeof( FancyElvenArmoire ), 1044292, 1072866, 80.0, 105.0, typeof( Log ), 1044041, 60, 1044351 );
|
||||
AddRecipe( index, 312 );
|
||||
ForceNonExceptional( index );
|
||||
SetNeededExpansion( index, Expansion.ML );
|
||||
*/
|
||||
index = AddCraft( typeof(FancyElvenArmoire), 1044292, 1072866, 80.0, 105.0, typeof(Log), 1044041, 60, 1044351);
|
||||
AddRecipe(index, 312);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(SimpleElvenArmoire), 1044292, 1073401, 80.0, 105.0, typeof(Log), 1044041, 60, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(RarewoodChest), 1044292, 1073402, 80.0, 105.0, typeof(Log), 1044041, 30, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(DecorativeBox), 1044292, 1073403, 80.0, 105.0, typeof(Log), 1044041, 25, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(FullBookcase), 1044292, 1071213, 60.0, 85.0, typeof(Log), 1044041, 25, 1044351);
|
||||
AddRes(index, typeof(AcademicBooksArtifact), 1071202, 1, 1044253);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
}
|
||||
|
||||
// Staves and Shields
|
||||
AddCraft(
|
||||
typeof(ShepherdsCrook),
|
||||
Core.ML ? 1044566 : 1044295,
|
||||
1023713,
|
||||
78.9,
|
||||
103.9,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
7,
|
||||
1044351
|
||||
);
|
||||
AddCraft(
|
||||
typeof(QuarterStaff),
|
||||
Core.ML ? 1044566 : 1044295,
|
||||
1023721,
|
||||
73.6,
|
||||
98.6,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
6,
|
||||
1044351
|
||||
);
|
||||
AddCraft(
|
||||
typeof(GnarledStaff),
|
||||
Core.ML ? 1044566 : 1044295,
|
||||
1025112,
|
||||
78.9,
|
||||
103.9,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
7,
|
||||
1044351
|
||||
);
|
||||
AddCraft(
|
||||
typeof(WoodenShield),
|
||||
Core.ML ? 1062760 : 1044295,
|
||||
1027034,
|
||||
52.6,
|
||||
77.6,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
9,
|
||||
1044351
|
||||
);
|
||||
AddCraft(typeof(ShepherdsCrook), Core.ML ? 1044566 : 1044295, 1023713, 78.9, 103.9, typeof(Log), 1044041, 7, 1044351);
|
||||
AddCraft(typeof(QuarterStaff), Core.ML ? 1044566 : 1044295, 1023721, 73.6, 98.6, typeof(Log), 1044041, 6, 1044351);
|
||||
AddCraft(typeof(GnarledStaff), Core.ML ? 1044566 : 1044295, 1025112, 78.9, 103.9, typeof(Log), 1044041, 7, 1044351);
|
||||
|
||||
if (!Core.AOS) // Duplicate Entries to preserve ordering depending on era
|
||||
{
|
||||
index = AddCraft(
|
||||
typeof(FishingPole),
|
||||
Core.ML ? 1044294 : 1044295,
|
||||
1023519,
|
||||
68.4,
|
||||
93.4,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
5,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(FishingPole), Core.ML ? 1044294 : 1044295, 1023519, 68.4, 93.4, typeof(Log), 1044041, 5, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 40.0, 45.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 5, 1044287);
|
||||
}
|
||||
|
||||
if (Core.SE)
|
||||
{
|
||||
index = AddCraft(
|
||||
typeof(Bokuto),
|
||||
Core.ML ? 1044566 : 1044295,
|
||||
1030227,
|
||||
70.0,
|
||||
95.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
6,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(Bokuto), Core.ML ? 1044566 : 1044295, 1030227, 70.0, 95.0, typeof(Log), 1044041, 6, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(Fukiya),
|
||||
Core.ML ? 1044566 : 1044295,
|
||||
1030229,
|
||||
60.0,
|
||||
85.0,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
6,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(Fukiya), Core.ML ? 1044566 : 1044295, 1030229, 60.0, 85.0, typeof(Log), 1044041, 6, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(Tetsubo),
|
||||
Core.ML ? 1044566 : 1044295,
|
||||
1030225,
|
||||
80.0,
|
||||
140.3,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
10,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(Tetsubo), Core.ML ? 1044566 : 1044295, 1030225, 80.0, 140.3, typeof(Log), 1044041, 10, 1044351);
|
||||
SetNeededExpansion(index, Expansion.SE);
|
||||
}
|
||||
|
||||
if (Core.ML)
|
||||
{
|
||||
index = AddCraft(typeof(WildStaff), 1044566, 1031557, 63.8, 113.8, typeof(Log), 1044041, 16, 1044351);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(PhantomStaff), 1044566, 1072919, 90.0, 130.0, typeof(Log), 1044041, 16, 1044351);
|
||||
AddRes(index, typeof(DiseasedBark), 1032683, 1, 1053098);
|
||||
AddRes(index, typeof(Putrefication), 1032678, 10, 1053098);
|
||||
|
|
@ -540,51 +331,56 @@ namespace Server.Engines.Craft
|
|||
AddRareRecipe(index, 313);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(ArcanistsWildStaff),
|
||||
1044566,
|
||||
1073549,
|
||||
63.8,
|
||||
113.8,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
16,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(ArcanistsWildStaff), 1044566, 1073549, 63.8, 113.8, typeof(Log), 1044041, 16, 1044351);
|
||||
AddRes(index, typeof(WhitePearl), 1026253, 1, 1053098);
|
||||
AddRecipe(index, 314);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(typeof(AncientWildStaff), 1044566, 1073550, 63.8, 113.8, typeof(Log), 1044041, 16, 1044351);
|
||||
AddRes(index, typeof(PerfectEmerald), 1026251, 1, 1053098);
|
||||
AddRecipe(index, 315);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(typeof(ThornedWildStaff), 1044566, 1073551, 63.8, 113.8, typeof(Log), 1044041, 16, 1044351);
|
||||
AddRes(index, typeof(FireRuby), 1026254, 1, 1053098);
|
||||
AddRecipe(index, 316);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(HardenedWildStaff),
|
||||
1044566,
|
||||
1073552,
|
||||
63.8,
|
||||
113.8,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
16,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(HardenedWildStaff), 1044566, 1073552, 63.8, 113.8, typeof(Log), 1044041, 16, 1044351);
|
||||
AddRes(index, typeof(Turquoise), 1026250, 1, 1053098);
|
||||
AddRecipe(index, 317);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
}
|
||||
|
||||
// Armor
|
||||
AddCraft(typeof(WoodenShield), Core.ML ? 1062760 : 1044295, 1027034, 52.6, 77.6, typeof(Log), 1044041, 9, 1044351);
|
||||
|
||||
if (Core.ML)
|
||||
{
|
||||
index = AddCraft(typeof(WoodlandChest), 1062760, 1031111, 90.0, 115.0, typeof(Log), 1044041, 20, 1044351);
|
||||
AddRes(index, typeof(BarkFragment), 1032687, 6, 1053098);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(WoodlandArms), 1062760, 1031116, 80.0, 105.0, typeof(Log), 1044041, 15, 1044351);
|
||||
AddRes(index, typeof(BarkFragment), 1032687, 4, 1053098);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(WoodlandGloves), 1062760, 1031114, 85.0, 110.0, typeof(Log), 1044041, 15, 1044351);
|
||||
AddRes(index, typeof(BarkFragment), 1032687, 4, 1053098);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(WoodlandLegs), 1062760, 1031115, 85.0, 110.0, typeof(Log), 1044041, 15, 1044351);
|
||||
AddRes(index, typeof(BarkFragment), 1032687, 4, 1053098);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(WoodlandGorget), 1062760, 1031113, 85.0, 110.0, typeof(Log), 1044041, 15, 1044351);
|
||||
AddRes(index, typeof(BarkFragment), 1032687, 4, 1053098);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(RavenHelm), 1062760, 1031121, 65.0, 115.0, typeof(Log), 1044041, 10, 1044351);
|
||||
AddRes(index, typeof(BarkFragment), 1032687, 4, 1053098);
|
||||
AddRes(index, typeof(Feather), 1027123, 25, 1053098);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(VultureHelm), 1062760, 1031122, 63.9, 113.9, typeof(Log), 1044041, 10, 1044351);
|
||||
AddRes(index, typeof(BarkFragment), 1032687, 4, 1053098);
|
||||
AddRes(index, typeof(Feather), 1027123, 25, 1053098);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(WingedHelm), 1062760, 1031123, 58.4, 108.4, typeof(Log), 1044041, 10, 1044351);
|
||||
AddRes(index, typeof(BarkFragment), 1032687, 4, 1053098);
|
||||
AddRes(index, typeof(Feather), 1027123, 60, 1053098);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(IronwoodCrown), 1062760, 1072924, 85.0, 120.0, typeof(Log), 1044041, 10, 1044351);
|
||||
AddRes(index, typeof(DiseasedBark), 1032683, 1, 1053098);
|
||||
AddRes(index, typeof(Corruption), 1032676, 10, 1053098);
|
||||
|
|
@ -592,7 +388,6 @@ namespace Server.Engines.Craft
|
|||
AddRareRecipe(index, 318);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(typeof(BrambleCoat), 1062760, 1072925, 85.0, 120.0, typeof(Log), 1044041, 10, 1044351);
|
||||
AddRes(index, typeof(DiseasedBark), 1032683, 1, 1053098);
|
||||
AddRes(index, typeof(Taint), 1032679, 10, 1053098);
|
||||
|
|
@ -606,23 +401,18 @@ namespace Server.Engines.Craft
|
|||
index = AddCraft(typeof(LapHarp), 1044293, 1023762, 63.1, 88.1, typeof(Log), 1044041, 20, 1044351);
|
||||
AddSkill(index, SkillName.Musicianship, 45.0, 50.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 10, 1044287);
|
||||
|
||||
index = AddCraft(typeof(Harp), 1044293, 1023761, 78.9, 103.9, typeof(Log), 1044041, 35, 1044351);
|
||||
AddSkill(index, SkillName.Musicianship, 45.0, 50.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 15, 1044287);
|
||||
|
||||
index = AddCraft(typeof(Drums), 1044293, 1023740, 57.8, 82.8, typeof(Log), 1044041, 20, 1044351);
|
||||
AddSkill(index, SkillName.Musicianship, 45.0, 50.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 10, 1044287);
|
||||
|
||||
index = AddCraft(typeof(Lute), 1044293, 1023763, 68.4, 93.4, typeof(Log), 1044041, 25, 1044351);
|
||||
AddSkill(index, SkillName.Musicianship, 45.0, 50.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 10, 1044287);
|
||||
|
||||
index = AddCraft(typeof(Tambourine), 1044293, 1023741, 57.8, 82.8, typeof(Log), 1044041, 15, 1044351);
|
||||
AddSkill(index, SkillName.Musicianship, 45.0, 50.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 10, 1044287);
|
||||
|
||||
index = AddCraft(typeof(TambourineTassel), 1044293, 1044320, 57.8, 82.8, typeof(Log), 1044041, 15, 1044351);
|
||||
AddSkill(index, SkillName.Musicianship, 45.0, 50.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 15, 1044287);
|
||||
|
|
@ -634,44 +424,58 @@ namespace Server.Engines.Craft
|
|||
SetNeededExpansion(index, Expansion.SE);
|
||||
}
|
||||
|
||||
// Misc
|
||||
if (Core.AOS)
|
||||
{
|
||||
AddCraft(typeof(PlayerBBEast), 1044290, 1062420, 85.0, 110.0, typeof(Log), 1044041, 50, 1044351);
|
||||
AddCraft(typeof(PlayerBBSouth), 1044290, 1062421, 85.0, 110.0, typeof(Log), 1044041, 50, 1044351);
|
||||
}
|
||||
|
||||
if (Core.ML)
|
||||
{
|
||||
index = AddCraft(
|
||||
typeof(TallElvenBedSouthDeed),
|
||||
1044290,
|
||||
1072858,
|
||||
94.7,
|
||||
119.7,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
200,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(ParrotPerchDeed), 1044290, 1072617, 50.0, 85.0, typeof(Log), 1044041, 100, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ArcaneCircleDeed), 1044290, 1072703, 94.7, 119.7, typeof(Log), 1044041, 100, 1044351);
|
||||
AddRes(index, typeof(BlueDiamond), 1026255, 2, 1053098);
|
||||
AddRes(index, typeof(PerfectEmerald), 1026251, 2, 1053098);
|
||||
AddRes(index, typeof(FireRuby), 1026254, 2, 1053098);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(TallElvenBedSouthDeed), 1044290, 1072858, 94.7, 119.7, typeof(Log), 1044041, 200, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 75.0, 80.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 100, 1044287);
|
||||
AddRecipe(index, 320);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
|
||||
index = AddCraft(
|
||||
typeof(TallElvenBedEastDeed),
|
||||
1044290,
|
||||
1072859,
|
||||
94.7,
|
||||
119.7,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
200,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(TallElvenBedEastDeed), 1044290, 1072859, 94.7, 119.7, typeof(Log), 1044041, 200, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 75.0, 80.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 100, 1044287);
|
||||
AddRecipe(index, 321);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenBedSouthDeed), 1044290, 1072860, 94.7, 119.7, typeof(Log), 1044041, 100, 1044351);
|
||||
AddRes(index, typeof(Cloth), 1044286, 100, 1044287);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenBedEastDeed), 1044290, 1072861, 94.7, 119.7, typeof(Log), 1044041, 100, 1044351);
|
||||
AddRes(index, typeof(Cloth), 1044286, 100, 1044287);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenLoveseatSouthDeed), 1044290, 1072867, 80.0, 105.0, typeof(Log), 1044041, 50, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenLoveseatEastDeed), 1044290, 1073372, 80.0, 105.0, typeof(Log), 1044041, 50, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(AlchemistTableSouthDeed), 1044290, 1073396, 85.0, 110.0, typeof(Log), 1044041, 70, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(AlchemistTableEastDeed), 1044290, 1073397, 85.0, 110.0, typeof(Log), 1044041, 70, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
}
|
||||
|
||||
// Misc
|
||||
index = AddCraft(typeof(SmallBedSouthDeed), 1044290, 1044321, 94.7, 119.8, typeof(Log), 1044041, 100, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 75.0, 80.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 100, 1044287);
|
||||
|
|
@ -694,13 +498,14 @@ namespace Server.Engines.Craft
|
|||
AddSkill(index, SkillName.Magery, 50.0, 55.0);
|
||||
AddRes(index, typeof(IronIngot), 1044036, 40, 1044037);
|
||||
|
||||
if (Core.AOS)
|
||||
// Blacksmithy - This changed to Anvils and Forges (1111809) for SA
|
||||
if (Core.ML)
|
||||
{
|
||||
AddCraft(typeof(PlayerBBEast), 1044290, 1062420, 85.0, 110.0, typeof(Log), 1044041, 50, 1044351);
|
||||
AddCraft(typeof(PlayerBBSouth), 1044290, 1062421, 85.0, 110.0, typeof(Log), 1044041, 50, 1044351);
|
||||
index = AddCraft(typeof(ElvenForgeDeed), 1044296, 1072875, 94.7, 119.7, typeof(Log), 1044041, 200, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
}
|
||||
|
||||
// Blacksmithy - This changed to Anvils and Forges (1111809) for SA
|
||||
index = AddCraft(typeof(SmallForgeDeed), 1044296, 1044330, 73.6, 98.6, typeof(Log), 1044041, 5, 1044351);
|
||||
AddSkill(index, SkillName.Blacksmith, 75.0, 80.0);
|
||||
AddRes(index, typeof(IronIngot), 1044036, 75, 1044037);
|
||||
|
|
@ -721,33 +526,13 @@ namespace Server.Engines.Craft
|
|||
index = AddCraft(typeof(TrainingDummyEastDeed), 1044297, 1044335, 68.4, 93.4, typeof(Log), 1044041, 55, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 50.0, 55.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 60, 1044287);
|
||||
index = AddCraft(
|
||||
typeof(TrainingDummySouthDeed),
|
||||
1044297,
|
||||
1044336,
|
||||
68.4,
|
||||
93.4,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
55,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(TrainingDummySouthDeed), 1044297, 1044336, 68.4, 93.4, typeof(Log), 1044041, 55, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 50.0, 55.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 60, 1044287);
|
||||
index = AddCraft(typeof(PickpocketDipEastDeed), 1044297, 1044337, 73.6, 98.6, typeof(Log), 1044041, 65, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 50.0, 55.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 60, 1044287);
|
||||
index = AddCraft(
|
||||
typeof(PickpocketDipSouthDeed),
|
||||
1044297,
|
||||
1044338,
|
||||
73.6,
|
||||
98.6,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
65,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(PickpocketDipSouthDeed), 1044297, 1044338, 73.6, 98.6, typeof(Log), 1044041, 65, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 50.0, 55.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 60, 1044287);
|
||||
|
||||
|
|
@ -755,20 +540,31 @@ namespace Server.Engines.Craft
|
|||
index = AddCraft(typeof(Dressform), 1044298, 1044339, 63.1, 88.1, typeof(Log), 1044041, 25, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 65.0, 70.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 10, 1044287);
|
||||
index = AddCraft(typeof(SpinningwheelEastDeed), 1044298, 1044341, 73.6, 98.6, typeof(Log), 1044041, 75, 1044351);
|
||||
|
||||
if (Core.ML)
|
||||
{
|
||||
index = AddCraft(typeof(ElvenSpinningWheelEastDeed), 1044298, 1073393, 75.0, 100.0, typeof(Log), 1044041, 60, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 65.0, 85.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 40, 1044287);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenSpinningWheelSouthDeed), 1044298, 1072878, 75.0, 100.0, typeof(Log), 1044041, 60, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 65.0, 85.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 40, 1044287);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenStoveSouthDeed), 1044298, 1073394, 85.0, 110.0, typeof(Log), 1044041, 80, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
index = AddCraft(typeof(ElvenStoveEastDeed), 1044298, 1073395, 85.0, 110.0, typeof(Log), 1044041, 80, 1044351);
|
||||
ForceNonExceptional(index);
|
||||
SetNeededExpansion(index, Expansion.ML);
|
||||
}
|
||||
|
||||
index = AddCraft(typeof(SpinningWheelEastDeed), 1044298, 1044341, 73.6, 98.6, typeof(Log), 1044041, 75, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 65.0, 70.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 25, 1044287);
|
||||
index = AddCraft(
|
||||
typeof(SpinningwheelSouthDeed),
|
||||
1044298,
|
||||
1044342,
|
||||
73.6,
|
||||
98.6,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
75,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(SpinningWheelSouthDeed), 1044298, 1044342, 73.6, 98.6, typeof(Log), 1044041, 75, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 65.0, 70.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 25, 1044287);
|
||||
index = AddCraft(typeof(LoomEastDeed), 1044298, 1044343, 84.2, 109.2, typeof(Log), 1044041, 85, 1044351);
|
||||
|
|
@ -777,82 +573,20 @@ namespace Server.Engines.Craft
|
|||
index = AddCraft(typeof(LoomSouthDeed), 1044298, 1044344, 84.2, 109.2, typeof(Log), 1044041, 85, 1044351);
|
||||
AddSkill(index, SkillName.Tailoring, 65.0, 70.0);
|
||||
AddRes(index, typeof(Cloth), 1044286, 25, 1044287);
|
||||
|
||||
// Cooking
|
||||
index = AddCraft(
|
||||
typeof(StoneOvenEastDeed),
|
||||
Core.ML ? 1044298 : 1044299,
|
||||
1044345,
|
||||
68.4,
|
||||
93.4,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
85,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(StoneOvenEastDeed), Core.ML ? 1044298 : 1044299, 1044345, 68.4, 93.4, typeof(Log), 1044041, 85, 1044351);
|
||||
AddSkill(index, SkillName.Tinkering, 50.0, 55.0);
|
||||
AddRes(index, typeof(IronIngot), 1044036, 125, 1044037);
|
||||
index = AddCraft(
|
||||
typeof(StoneOvenSouthDeed),
|
||||
Core.ML ? 1044298 : 1044299,
|
||||
1044346,
|
||||
68.4,
|
||||
93.4,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
85,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(StoneOvenSouthDeed), Core.ML ? 1044298 : 1044299, 1044346, 68.4, 93.4, typeof(Log), 1044041, 85, 1044351);
|
||||
AddSkill(index, SkillName.Tinkering, 50.0, 55.0);
|
||||
AddRes(index, typeof(IronIngot), 1044036, 125, 1044037);
|
||||
index = AddCraft(
|
||||
typeof(FlourMillEastDeed),
|
||||
Core.ML ? 1044298 : 1044299,
|
||||
1044347,
|
||||
94.7,
|
||||
119.7,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
100,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(FlourMillEastDeed), Core.ML ? 1044298 : 1044299, 1044347, 94.7, 119.7, typeof(Log), 1044041, 100, 1044351);
|
||||
AddSkill(index, SkillName.Tinkering, 50.0, 55.0);
|
||||
AddRes(index, typeof(IronIngot), 1044036, 50, 1044037);
|
||||
index = AddCraft(
|
||||
typeof(FlourMillSouthDeed),
|
||||
Core.ML ? 1044298 : 1044299,
|
||||
1044348,
|
||||
94.7,
|
||||
119.7,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
100,
|
||||
1044351
|
||||
);
|
||||
index = AddCraft(typeof(FlourMillSouthDeed), Core.ML ? 1044298 : 1044299, 1044348, 94.7, 119.7, typeof(Log), 1044041, 100, 1044351);
|
||||
AddSkill(index, SkillName.Tinkering, 50.0, 55.0);
|
||||
AddRes(index, typeof(IronIngot), 1044036, 50, 1044037);
|
||||
AddCraft(
|
||||
typeof(WaterTroughEastDeed),
|
||||
Core.ML ? 1044298 : 1044299,
|
||||
1044349,
|
||||
94.7,
|
||||
119.7,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
150,
|
||||
1044351
|
||||
);
|
||||
AddCraft(
|
||||
typeof(WaterTroughSouthDeed),
|
||||
Core.ML ? 1044298 : 1044299,
|
||||
1044350,
|
||||
94.7,
|
||||
119.7,
|
||||
typeof(Log),
|
||||
1044041,
|
||||
150,
|
||||
1044351
|
||||
);
|
||||
AddCraft(typeof(WaterTroughEastDeed), Core.ML ? 1044298 : 1044299, 1044349, 94.7, 119.7, typeof(Log), 1044041, 150, 1044351);
|
||||
AddCraft(typeof(WaterTroughSouthDeed), Core.ML ? 1044298 : 1044299, 1044350, 94.7, 119.7, typeof(Log), 1044041, 150, 1044351);
|
||||
|
||||
MarkOption = true;
|
||||
Repair = Core.AOS;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ namespace Server.Engines.Craft
|
|||
|
||||
public override double GetChanceAtMin(CraftItem item)
|
||||
{
|
||||
if (item.NameNumber == 1044258 || item.NameNumber == 1046445) // potion keg and faction trap removal kit
|
||||
if (item.NameNumber is 1044258 or 1046445) // potion keg and faction trap removal kit
|
||||
{
|
||||
return 0.5; // 50%
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,22 +24,23 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
private const int PlaneHeight = 20;
|
||||
public static PathAlgorithm Instance = new FastAStarAlgorithm();
|
||||
|
||||
private static readonly Direction[] m_Path = new Direction[AreaSize * AreaSize];
|
||||
private static readonly PathNode[] m_Nodes = new PathNode[NodeCount];
|
||||
private static readonly BitArray m_Touched = new(NodeCount);
|
||||
private static readonly BitArray m_OnOpen = new(NodeCount);
|
||||
private static readonly int[] m_Successors = new int[8];
|
||||
private static readonly Direction[] _path = new Direction[AreaSize * AreaSize];
|
||||
private static readonly PathNode[] _nodes = new PathNode[NodeCount];
|
||||
private static readonly BitArray _touched = new(NodeCount);
|
||||
private static readonly BitArray _onOpen = new(NodeCount);
|
||||
private static readonly int[] _successors = new int[8];
|
||||
|
||||
private static int m_xOffset, m_yOffset;
|
||||
private static int m_OpenList;
|
||||
private static int _xOffset;
|
||||
private static int _yOffset;
|
||||
private static int _openList;
|
||||
|
||||
private Point3D m_Goal;
|
||||
private Point3D _goal;
|
||||
|
||||
public int Heuristic(int x, int y, int z)
|
||||
{
|
||||
x -= m_Goal.X - m_xOffset;
|
||||
y -= m_Goal.Y - m_yOffset;
|
||||
z -= m_Goal.Z;
|
||||
x -= _goal.X - _xOffset;
|
||||
y -= _goal.Y - _yOffset;
|
||||
z -= _goal.Z;
|
||||
|
||||
x *= 11;
|
||||
y *= 11;
|
||||
|
|
@ -52,59 +53,59 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
|
||||
private void RemoveFromChain(int node)
|
||||
{
|
||||
if (node < 0 || node >= NodeCount)
|
||||
if (node is < 0 or >= NodeCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_Touched[node] || !m_OnOpen[node])
|
||||
if (!_touched[node] || !_onOpen[node])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var prev = m_Nodes[node].prev;
|
||||
var next = m_Nodes[node].next;
|
||||
var prev = _nodes[node].prev;
|
||||
var next = _nodes[node].next;
|
||||
|
||||
if (m_OpenList == node)
|
||||
if (_openList == node)
|
||||
{
|
||||
m_OpenList = next;
|
||||
_openList = next;
|
||||
}
|
||||
|
||||
if (prev != -1)
|
||||
{
|
||||
m_Nodes[prev].next = next;
|
||||
_nodes[prev].next = next;
|
||||
}
|
||||
|
||||
if (next != -1)
|
||||
{
|
||||
m_Nodes[next].prev = prev;
|
||||
_nodes[next].prev = prev;
|
||||
}
|
||||
|
||||
m_Nodes[node].prev = -1;
|
||||
m_Nodes[node].next = -1;
|
||||
_nodes[node].prev = -1;
|
||||
_nodes[node].next = -1;
|
||||
}
|
||||
|
||||
private void AddToChain(int node)
|
||||
{
|
||||
if (node < 0 || node >= NodeCount)
|
||||
if (node is < 0 or >= NodeCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveFromChain(node);
|
||||
|
||||
if (m_OpenList != -1)
|
||||
if (_openList != -1)
|
||||
{
|
||||
m_Nodes[m_OpenList].prev = node;
|
||||
_nodes[_openList].prev = node;
|
||||
}
|
||||
|
||||
m_Nodes[node].next = m_OpenList;
|
||||
m_Nodes[node].prev = -1;
|
||||
_nodes[node].next = _openList;
|
||||
_nodes[node].prev = -1;
|
||||
|
||||
m_OpenList = node;
|
||||
_openList = node;
|
||||
|
||||
m_Touched[node] = true;
|
||||
m_OnOpen[node] = true;
|
||||
_touched[node] = true;
|
||||
_onOpen[node] = true;
|
||||
}
|
||||
|
||||
public override Direction[] Find(Mobile m, Map map, Point3D start, Point3D goal)
|
||||
|
|
@ -114,37 +115,38 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
return null;
|
||||
}
|
||||
|
||||
m_Touched.SetAll(false);
|
||||
_touched.SetAll(false);
|
||||
_onOpen.SetAll(false);
|
||||
|
||||
m_Goal = goal;
|
||||
_goal = goal;
|
||||
|
||||
m_xOffset = (start.X + goal.X - AreaSize) / 2;
|
||||
m_yOffset = (start.Y + goal.Y - AreaSize) / 2;
|
||||
_xOffset = (start.X + goal.X - AreaSize) / 2;
|
||||
_yOffset = (start.Y + goal.Y - AreaSize) / 2;
|
||||
|
||||
var fromNode = GetIndex(start.X, start.Y, start.Z);
|
||||
var destNode = GetIndex(goal.X, goal.Y, goal.Z);
|
||||
|
||||
m_OpenList = fromNode;
|
||||
_openList = fromNode;
|
||||
|
||||
m_Nodes[m_OpenList].cost = 0;
|
||||
m_Nodes[m_OpenList].total = Heuristic(start.X - m_xOffset, start.Y - m_yOffset, start.Z);
|
||||
m_Nodes[m_OpenList].parent = -1;
|
||||
m_Nodes[m_OpenList].next = -1;
|
||||
m_Nodes[m_OpenList].prev = -1;
|
||||
m_Nodes[m_OpenList].z = start.Z;
|
||||
_nodes[_openList].cost = 0;
|
||||
_nodes[_openList].total = Heuristic(start.X - _xOffset, start.Y - _yOffset, start.Z);
|
||||
_nodes[_openList].parent = -1;
|
||||
_nodes[_openList].next = -1;
|
||||
_nodes[_openList].prev = -1;
|
||||
_nodes[_openList].z = start.Z;
|
||||
|
||||
m_OnOpen[m_OpenList] = true;
|
||||
m_Touched[m_OpenList] = true;
|
||||
_onOpen[_openList] = true;
|
||||
_touched[_openList] = true;
|
||||
|
||||
var bc = m as BaseCreature;
|
||||
|
||||
int backtrack = 0, depth = 0;
|
||||
|
||||
var path = m_Path;
|
||||
var path = _path;
|
||||
|
||||
while (m_OpenList != -1)
|
||||
while (_openList != -1)
|
||||
{
|
||||
var bestNode = FindBest(m_OpenList);
|
||||
var bestNode = FindBest(_openList);
|
||||
|
||||
if (++depth > MaxDepth)
|
||||
{
|
||||
|
|
@ -159,7 +161,7 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
|
||||
MoveImpl.Goal = goal;
|
||||
|
||||
var vals = m_Successors;
|
||||
var vals = _successors;
|
||||
var count = GetSuccessors(bestNode, m, map);
|
||||
|
||||
MoveImpl.AlwaysIgnoreDoors = false;
|
||||
|
|
@ -175,30 +177,25 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
{
|
||||
var newNode = vals[i];
|
||||
|
||||
var wasTouched = m_Touched[newNode];
|
||||
var wasTouched = _touched[newNode];
|
||||
|
||||
if (wasTouched)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var newCost = m_Nodes[bestNode].cost + 1;
|
||||
var newCost = _nodes[bestNode].cost + 1;
|
||||
var newTotal = newCost + Heuristic(
|
||||
newNode % AreaSize,
|
||||
newNode / AreaSize % AreaSize,
|
||||
m_Nodes[newNode].z
|
||||
_nodes[newNode].z
|
||||
);
|
||||
|
||||
if (m_Nodes[newNode].total <= newTotal)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_nodes[newNode].parent = bestNode;
|
||||
_nodes[newNode].cost = newCost;
|
||||
_nodes[newNode].total = newTotal;
|
||||
|
||||
m_Nodes[newNode].parent = bestNode;
|
||||
m_Nodes[newNode].cost = newCost;
|
||||
m_Nodes[newNode].total = newTotal;
|
||||
|
||||
if (m_OnOpen[newNode])
|
||||
if (_onOpen[newNode])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -211,7 +208,7 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
}
|
||||
|
||||
var pathCount = 0;
|
||||
var parent = m_Nodes[newNode].parent;
|
||||
var parent = _nodes[newNode].parent;
|
||||
|
||||
while (parent != -1)
|
||||
{
|
||||
|
|
@ -222,7 +219,7 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
newNode / AreaSize % AreaSize
|
||||
);
|
||||
newNode = parent;
|
||||
parent = m_Nodes[newNode].parent;
|
||||
parent = _nodes[newNode].parent;
|
||||
|
||||
if (newNode == fromNode)
|
||||
{
|
||||
|
|
@ -246,8 +243,8 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
|
||||
private int GetIndex(int x, int y, int z)
|
||||
{
|
||||
x -= m_xOffset;
|
||||
y -= m_yOffset;
|
||||
x -= _xOffset;
|
||||
y -= _yOffset;
|
||||
z += PlaneOffset;
|
||||
z /= PlaneHeight;
|
||||
|
||||
|
|
@ -256,24 +253,24 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
|
||||
private int FindBest(int node)
|
||||
{
|
||||
var least = m_Nodes[node].total;
|
||||
var least = _nodes[node].total;
|
||||
var leastNode = node;
|
||||
|
||||
while (node != -1)
|
||||
{
|
||||
if (m_Nodes[node].total < least)
|
||||
if (_nodes[node].total < least)
|
||||
{
|
||||
least = m_Nodes[node].total;
|
||||
least = _nodes[node].total;
|
||||
leastNode = node;
|
||||
}
|
||||
|
||||
node = m_Nodes[node].next;
|
||||
node = _nodes[node].next;
|
||||
}
|
||||
|
||||
RemoveFromChain(leastNode);
|
||||
|
||||
m_Touched[leastNode] = true;
|
||||
m_OnOpen[leastNode] = false;
|
||||
_touched[leastNode] = true;
|
||||
_onOpen[leastNode] = false;
|
||||
|
||||
return leastNode;
|
||||
}
|
||||
|
|
@ -282,11 +279,11 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
{
|
||||
var px = p % AreaSize;
|
||||
var py = p / AreaSize % AreaSize;
|
||||
var pz = m_Nodes[p].z;
|
||||
var pz = _nodes[p].z;
|
||||
|
||||
var p3D = new Point3D(px + m_xOffset, py + m_yOffset, pz);
|
||||
var p3D = new Point3D(px + _xOffset, py + _yOffset, pz);
|
||||
|
||||
var vals = m_Successors;
|
||||
var vals = _successors;
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < 8; ++i)
|
||||
|
|
@ -332,18 +329,18 @@ namespace Server.PathAlgorithms.FastAStar
|
|||
x += px;
|
||||
y += py;
|
||||
|
||||
if (x < 0 || x >= AreaSize || y < 0 || y >= AreaSize)
|
||||
if (x is < 0 or >= AreaSize || y is < 0 or >= AreaSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CalcMoves.CheckMovement(m, map, p3D, (Direction)i, out var z))
|
||||
{
|
||||
var idx = GetIndex(x + m_xOffset, y + m_yOffset, z);
|
||||
var idx = GetIndex(x + _xOffset, y + _yOffset, z);
|
||||
|
||||
if (idx >= 0 && idx < NodeCount)
|
||||
{
|
||||
m_Nodes[idx].z = z;
|
||||
_nodes[idx].z = z;
|
||||
vals[count++] = idx;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,674 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Movement
|
||||
{
|
||||
public class FastMovementImpl : IMovementImpl
|
||||
{
|
||||
private const int PersonHeight = 16;
|
||||
private const int StepHeight = 2;
|
||||
|
||||
private const TileFlag ImpassableSurface = TileFlag.Impassable | TileFlag.Surface;
|
||||
public static bool Enabled = false;
|
||||
|
||||
private static IMovementImpl _Successor;
|
||||
|
||||
private FastMovementImpl()
|
||||
{
|
||||
}
|
||||
|
||||
public bool CheckMovement(Mobile m, Map map, Point3D loc, Direction d, out int newZ)
|
||||
{
|
||||
if (!Enabled && _Successor != null)
|
||||
{
|
||||
return _Successor.CheckMovement(m, map, loc, d, out newZ);
|
||||
}
|
||||
|
||||
if (map == null || map == Map.Internal)
|
||||
{
|
||||
newZ = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
var xStart = loc.X;
|
||||
var yStart = loc.Y;
|
||||
|
||||
int xForward = xStart, yForward = yStart;
|
||||
int xRight = xStart, yRight = yStart;
|
||||
int xLeft = xStart, yLeft = yStart;
|
||||
|
||||
var checkDiagonals = ((int)d & 0x1) == 0x1;
|
||||
|
||||
Offset(d, ref xForward, ref yForward);
|
||||
Offset((Direction)(((int)d - 1) & 0x7), ref xLeft, ref yLeft);
|
||||
Offset((Direction)(((int)d + 1) & 0x7), ref xRight, ref yRight);
|
||||
|
||||
if (xForward < 0 || yForward < 0 || xForward >= map.Width || yForward >= map.Height)
|
||||
{
|
||||
newZ = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
IEnumerable<Item> itemsStart, itemsForward, itemsLeft, itemsRight;
|
||||
|
||||
var ignoreMovableImpassables = MovementImpl.IgnoreMovableImpassables;
|
||||
var reqFlags = ImpassableSurface;
|
||||
|
||||
if (m.CanSwim)
|
||||
{
|
||||
reqFlags |= TileFlag.Wet;
|
||||
}
|
||||
|
||||
if (checkDiagonals)
|
||||
{
|
||||
var sStart = map.GetSector(xStart, yStart);
|
||||
var sForward = map.GetSector(xForward, yForward);
|
||||
var sLeft = map.GetSector(xLeft, yLeft);
|
||||
var sRight = map.GetSector(xRight, yRight);
|
||||
|
||||
itemsStart = sStart.Items.Where(i => Verify(i, reqFlags, ignoreMovableImpassables, xStart, yStart));
|
||||
itemsForward = sForward.Items.Where(i => Verify(i, reqFlags, ignoreMovableImpassables, xForward, yForward));
|
||||
itemsLeft = sLeft.Items.Where(i => Verify(i, reqFlags, ignoreMovableImpassables, xLeft, yLeft));
|
||||
itemsRight = sRight.Items.Where(i => Verify(i, reqFlags, ignoreMovableImpassables, xRight, yRight));
|
||||
}
|
||||
else
|
||||
{
|
||||
var sStart = map.GetSector(xStart, yStart);
|
||||
var sForward = map.GetSector(xForward, yForward);
|
||||
|
||||
itemsStart = sStart.Items.Where(i => Verify(i, reqFlags, ignoreMovableImpassables, xStart, yStart));
|
||||
itemsForward = sForward.Items.Where(i => Verify(i, reqFlags, ignoreMovableImpassables, xForward, yForward));
|
||||
itemsLeft = Enumerable.Empty<Item>();
|
||||
itemsRight = Enumerable.Empty<Item>();
|
||||
}
|
||||
|
||||
GetStartZ(m, map, loc, itemsStart, out var startZ, out var startTop);
|
||||
|
||||
List<Item> list = null;
|
||||
|
||||
MovementPool.AcquireMoveCache(ref list, itemsForward);
|
||||
|
||||
var moveIsOk = Check(map, m, list, xForward, yForward, startTop, startZ, m.CanSwim, m.CantWalk, out newZ);
|
||||
|
||||
if (moveIsOk && checkDiagonals)
|
||||
{
|
||||
if (m.Player && m.AccessLevel < AccessLevel.GameMaster)
|
||||
{
|
||||
MovementPool.AcquireMoveCache(ref list, itemsLeft);
|
||||
|
||||
if (!Check(map, m, list, xLeft, yLeft, startTop, startZ, m.CanSwim, m.CantWalk, out _))
|
||||
{
|
||||
moveIsOk = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
MovementPool.AcquireMoveCache(ref list, itemsRight);
|
||||
|
||||
if (!Check(map, m, list, xRight, yRight, startTop, startZ, m.CanSwim, m.CantWalk, out _))
|
||||
{
|
||||
moveIsOk = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MovementPool.AcquireMoveCache(ref list, itemsLeft);
|
||||
|
||||
if (!Check(map, m, list, xLeft, yLeft, startTop, startZ, m.CanSwim, m.CantWalk, out _))
|
||||
{
|
||||
MovementPool.AcquireMoveCache(ref list, itemsRight);
|
||||
|
||||
if (!Check(map, m, list, xRight, yRight, startTop, startZ, m.CanSwim, m.CantWalk, out _))
|
||||
{
|
||||
moveIsOk = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MovementPool.ClearMoveCache(ref list, true);
|
||||
|
||||
if (!moveIsOk)
|
||||
{
|
||||
newZ = startZ;
|
||||
}
|
||||
|
||||
return moveIsOk;
|
||||
}
|
||||
|
||||
public bool CheckMovement(Mobile m, Direction d, out int newZ) =>
|
||||
!Enabled && _Successor != null
|
||||
? _Successor.CheckMovement(m, d, out newZ)
|
||||
: CheckMovement(m, m.Map, m.Location, d, out newZ);
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
_Successor = Movement.Impl;
|
||||
Movement.Impl = new FastMovementImpl();
|
||||
}
|
||||
|
||||
private static bool IsOk(StaticTile tile, int ourZ, int ourTop)
|
||||
{
|
||||
var itemData = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
|
||||
|
||||
return tile.Z + itemData.CalcHeight <= ourZ || ourTop <= tile.Z || (itemData.Flags & ImpassableSurface) == 0;
|
||||
}
|
||||
|
||||
private static bool IsOk(Item item, int ourZ, int ourTop, bool ignoreDoors, bool ignoreSpellFields)
|
||||
{
|
||||
var itemID = item.ItemID & TileData.MaxItemValue;
|
||||
var itemData = TileData.ItemTable[itemID];
|
||||
|
||||
if ((itemData.Flags & ImpassableSurface) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (((itemData.Flags & TileFlag.Door) != 0 || itemID == 0x692 || itemID == 0x846 || itemID == 0x873 ||
|
||||
itemID >= 0x6F5 && itemID <= 0x6F6) && ignoreDoors)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((itemID == 0x82 || itemID == 0x3946 || itemID == 0x3956) && ignoreSpellFields)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return item.Z + itemData.CalcHeight <= ourZ || ourTop <= item.Z;
|
||||
}
|
||||
|
||||
private static bool IsOk(
|
||||
bool ignoreDoors,
|
||||
bool ignoreSpellFields,
|
||||
int ourZ,
|
||||
int ourTop,
|
||||
IEnumerable<StaticTile> tiles,
|
||||
IEnumerable<Item> items
|
||||
)
|
||||
{
|
||||
return tiles.All(t => IsOk(t, ourZ, ourTop)) &&
|
||||
items.All(i => IsOk(i, ourZ, ourTop, ignoreDoors, ignoreSpellFields));
|
||||
}
|
||||
|
||||
private static bool Check(
|
||||
Map map,
|
||||
Mobile m,
|
||||
List<Item> items,
|
||||
int x,
|
||||
int y,
|
||||
int startTop,
|
||||
int startZ,
|
||||
bool canSwim,
|
||||
bool cantWalk,
|
||||
out int newZ
|
||||
)
|
||||
{
|
||||
newZ = 0;
|
||||
|
||||
var tiles = map.Tiles.GetStaticTiles(x, y, true);
|
||||
var landTile = map.Tiles.GetLandTile(x, y);
|
||||
var landData = TileData.LandTable[landTile.ID & TileData.MaxLandValue];
|
||||
var considerLand = !landTile.Ignored;
|
||||
var landBlocks = (landData.Flags & TileFlag.Impassable) != 0;
|
||||
|
||||
if (landBlocks && canSwim && (landData.Flags & TileFlag.Wet) != 0)
|
||||
{
|
||||
landBlocks = false;
|
||||
}
|
||||
else if (cantWalk && (landData.Flags & TileFlag.Wet) == 0)
|
||||
{
|
||||
landBlocks = true;
|
||||
}
|
||||
|
||||
map.GetAverageZ(x, y, out var landZ, out var landCenter, out _);
|
||||
|
||||
var moveIsOk = false;
|
||||
|
||||
var stepTop = startTop + StepHeight;
|
||||
var checkTop = startZ + PersonHeight;
|
||||
|
||||
var ignoreDoors = MovementImpl.AlwaysIgnoreDoors || !m.Alive || m.IsDeadBondedPet || m.Body.IsGhost ||
|
||||
m.Body.BodyID == 987;
|
||||
var ignoreSpellFields = m is PlayerMobile && map.MapID != 0;
|
||||
|
||||
int itemZ, itemTop, ourZ, ourTop, testTop;
|
||||
ItemData itemData;
|
||||
TileFlag flags;
|
||||
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
itemData = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
|
||||
|
||||
if (m.Flying && itemData.Name.InsensitiveEquals("hover over"))
|
||||
{
|
||||
newZ = tile.Z;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stygian Dragon
|
||||
if (m.Body == 826 && map?.MapID == 5)
|
||||
{
|
||||
if (x >= 307 && x <= 354 && y >= 126 && y <= 192)
|
||||
{
|
||||
if (tile.Z > newZ)
|
||||
{
|
||||
newZ = tile.Z;
|
||||
}
|
||||
|
||||
moveIsOk = true;
|
||||
}
|
||||
else if (x >= 42 && x <= 89)
|
||||
{
|
||||
if (y >= 333 && y <= 399 || y >= 531 && y <= 597 || y >= 739 && y <= 805)
|
||||
{
|
||||
if (tile.Z > newZ)
|
||||
{
|
||||
newZ = tile.Z;
|
||||
}
|
||||
|
||||
moveIsOk = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flags = itemData.Flags;
|
||||
|
||||
if ((flags & ImpassableSurface) != TileFlag.Surface && (!canSwim || (flags & TileFlag.Wet) == 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cantWalk && (flags & TileFlag.Wet) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemZ = tile.Z;
|
||||
itemTop = itemZ;
|
||||
ourZ = itemZ + itemData.CalcHeight;
|
||||
ourTop = ourZ + PersonHeight;
|
||||
testTop = checkTop;
|
||||
|
||||
if (moveIsOk)
|
||||
{
|
||||
var cmp = (ourZ - m.Z).Abs() - (newZ - m.Z).Abs();
|
||||
|
||||
if (cmp > 0 || cmp == 0 && ourZ > newZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ourTop > testTop)
|
||||
{
|
||||
testTop = ourTop;
|
||||
}
|
||||
|
||||
if (!itemData.Bridge)
|
||||
{
|
||||
itemTop += itemData.Height;
|
||||
}
|
||||
|
||||
if (stepTop < itemTop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var landCheck = itemZ;
|
||||
|
||||
if (itemData.Height >= StepHeight)
|
||||
{
|
||||
landCheck += StepHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
landCheck += itemData.Height;
|
||||
}
|
||||
|
||||
if (considerLand && landCheck < landCenter && landCenter > ourZ && testTop > landZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsOk(ignoreDoors, ignoreSpellFields, ourZ, testTop, tiles, items))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
newZ = ourZ;
|
||||
moveIsOk = true;
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
itemData = item.ItemData;
|
||||
flags = itemData.Flags;
|
||||
|
||||
if (m.Flying && itemData.Name.InsensitiveEquals("hover over"))
|
||||
{
|
||||
newZ = item.Z;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.Movable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((flags & ImpassableSurface) != TileFlag.Surface && (!m.CanSwim || (flags & TileFlag.Wet) == 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cantWalk && (flags & TileFlag.Wet) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemZ = item.Z;
|
||||
itemTop = itemZ;
|
||||
ourZ = itemZ + itemData.CalcHeight;
|
||||
ourTop = ourZ + PersonHeight;
|
||||
testTop = checkTop;
|
||||
|
||||
if (moveIsOk)
|
||||
{
|
||||
var cmp = (ourZ - m.Z).Abs() - (newZ - m.Z).Abs();
|
||||
|
||||
if (cmp > 0 || cmp == 0 && ourZ > newZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ourTop > testTop)
|
||||
{
|
||||
testTop = ourTop;
|
||||
}
|
||||
|
||||
if (!itemData.Bridge)
|
||||
{
|
||||
itemTop += itemData.Height;
|
||||
}
|
||||
|
||||
if (stepTop < itemTop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var landCheck = itemZ;
|
||||
|
||||
if (itemData.Height >= StepHeight)
|
||||
{
|
||||
landCheck += StepHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
landCheck += itemData.Height;
|
||||
}
|
||||
|
||||
if (considerLand && landCheck < landCenter && landCenter > ourZ && testTop > landZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsOk(ignoreDoors, ignoreSpellFields, ourZ, testTop, tiles, items))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
newZ = ourZ;
|
||||
moveIsOk = true;
|
||||
}
|
||||
|
||||
if (!considerLand || landBlocks || stepTop < landZ)
|
||||
{
|
||||
return moveIsOk;
|
||||
}
|
||||
|
||||
ourZ = landCenter;
|
||||
ourTop = ourZ + PersonHeight;
|
||||
testTop = checkTop;
|
||||
|
||||
if (ourTop > testTop)
|
||||
{
|
||||
testTop = ourTop;
|
||||
}
|
||||
|
||||
var shouldCheck = true;
|
||||
|
||||
if (moveIsOk)
|
||||
{
|
||||
var cmp = (ourZ - m.Z).Abs() - (newZ - m.Z).Abs();
|
||||
|
||||
if (cmp > 0 || cmp == 0 && ourZ > newZ)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldCheck || !IsOk(ignoreDoors, ignoreSpellFields, ourZ, testTop, tiles, items))
|
||||
{
|
||||
return moveIsOk;
|
||||
}
|
||||
|
||||
newZ = ourZ;
|
||||
moveIsOk = true;
|
||||
|
||||
return moveIsOk;
|
||||
}
|
||||
|
||||
private static bool Verify(Item item, int x, int y) => item.AtWorldPoint(x, y);
|
||||
|
||||
private static bool Verify(Item item, TileFlag reqFlags, bool ignoreMovableImpassables) =>
|
||||
item != null && (!ignoreMovableImpassables || !item.Movable || !item.ItemData.Impassable) &&
|
||||
(item.ItemData.Flags & reqFlags) != 0 && !(item is BaseMulti) && item.ItemID <= TileData.MaxItemValue;
|
||||
|
||||
private static bool Verify(Item item, TileFlag reqFlags, bool ignoreMovableImpassables, int x, int y) =>
|
||||
Verify(item, reqFlags, ignoreMovableImpassables) && Verify(item, x, y);
|
||||
|
||||
private static void GetStartZ(Mobile m, Map map, Point3D loc, IEnumerable<Item> itemList, out int zLow, out int zTop)
|
||||
{
|
||||
int xCheck = loc.X, yCheck = loc.Y;
|
||||
|
||||
var landTile = map.Tiles.GetLandTile(xCheck, yCheck);
|
||||
var landData = TileData.LandTable[landTile.ID & TileData.MaxLandValue];
|
||||
var landBlocks = (landData.Flags & TileFlag.Impassable) != 0;
|
||||
|
||||
if (landBlocks && m.CanSwim && (landData.Flags & TileFlag.Wet) != 0)
|
||||
{
|
||||
landBlocks = false;
|
||||
}
|
||||
else if (m.CantWalk && (landData.Flags & TileFlag.Wet) == 0)
|
||||
{
|
||||
landBlocks = true;
|
||||
}
|
||||
|
||||
map.GetAverageZ(xCheck, yCheck, out var landZ, out var landCenter, out var landTop);
|
||||
|
||||
var considerLand = !landTile.Ignored;
|
||||
|
||||
var zCenter = zLow = zTop = 0;
|
||||
var isSet = false;
|
||||
|
||||
if (considerLand && !landBlocks && loc.Z >= landCenter)
|
||||
{
|
||||
zLow = landZ;
|
||||
zCenter = landCenter;
|
||||
zTop = landTop;
|
||||
isSet = true;
|
||||
}
|
||||
|
||||
var staticTiles = map.Tiles.GetStaticTiles(xCheck, yCheck, true);
|
||||
|
||||
foreach (var tile in staticTiles)
|
||||
{
|
||||
var tileData = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
|
||||
var calcTop = tile.Z + tileData.CalcHeight;
|
||||
|
||||
if (isSet && calcTop < zCenter)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tileData.Flags & TileFlag.Surface) == 0 &&
|
||||
(!m.CanSwim || (tileData.Flags & TileFlag.Wet) == 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (loc.Z < calcTop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m.CantWalk && (tileData.Flags & TileFlag.Wet) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
zLow = tile.Z;
|
||||
zCenter = calcTop;
|
||||
|
||||
var top = tile.Z + tileData.Height;
|
||||
|
||||
if (!isSet || top > zTop)
|
||||
{
|
||||
zTop = top;
|
||||
}
|
||||
|
||||
isSet = true;
|
||||
}
|
||||
|
||||
foreach (var item in itemList)
|
||||
{
|
||||
var itemData = item.ItemData;
|
||||
|
||||
var calcTop = item.Z + itemData.CalcHeight;
|
||||
|
||||
if (isSet && calcTop < zCenter)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((itemData.Flags & TileFlag.Surface) == 0 &&
|
||||
(!m.CanSwim || (itemData.Flags & TileFlag.Wet) == 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (loc.Z < calcTop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m.CantWalk && (itemData.Flags & TileFlag.Wet) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
zLow = item.Z;
|
||||
zCenter = calcTop;
|
||||
|
||||
var top = item.Z + itemData.Height;
|
||||
|
||||
if (!isSet || top > zTop)
|
||||
{
|
||||
zTop = top;
|
||||
}
|
||||
|
||||
isSet = true;
|
||||
}
|
||||
|
||||
if (!isSet)
|
||||
{
|
||||
zLow = zTop = loc.Z;
|
||||
}
|
||||
else if (loc.Z > zTop)
|
||||
{
|
||||
zTop = loc.Z;
|
||||
}
|
||||
}
|
||||
|
||||
public void Offset(Direction d, ref int x, ref int y)
|
||||
{
|
||||
switch (d & Direction.Mask)
|
||||
{
|
||||
case Direction.North:
|
||||
--y;
|
||||
break;
|
||||
case Direction.South:
|
||||
++y;
|
||||
break;
|
||||
case Direction.West:
|
||||
--x;
|
||||
break;
|
||||
case Direction.East:
|
||||
++x;
|
||||
break;
|
||||
case Direction.Right:
|
||||
++x;
|
||||
--y;
|
||||
break;
|
||||
case Direction.Left:
|
||||
--x;
|
||||
++y;
|
||||
break;
|
||||
case Direction.Down:
|
||||
++x;
|
||||
++y;
|
||||
break;
|
||||
case Direction.Up:
|
||||
--x;
|
||||
--y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MovementPool
|
||||
{
|
||||
private static readonly object _MovePoolLock = new();
|
||||
private static readonly Queue<List<Item>> _MoveCachePool = new(0x400);
|
||||
|
||||
public static void AcquireMoveCache(ref List<Item> cache, IEnumerable<Item> items)
|
||||
{
|
||||
if (cache == null)
|
||||
{
|
||||
lock (_MovePoolLock)
|
||||
{
|
||||
cache = _MoveCachePool.Count > 0 ? _MoveCachePool.Dequeue() : new List<Item>(0x10);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.Clear();
|
||||
}
|
||||
|
||||
cache.AddRange(items);
|
||||
}
|
||||
|
||||
public static void ClearMoveCache(ref List<Item> cache, bool free)
|
||||
{
|
||||
cache?.Clear();
|
||||
|
||||
if (!free)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_MovePoolLock)
|
||||
{
|
||||
if (_MoveCachePool.Count < 0x400)
|
||||
{
|
||||
_MoveCachePool.Enqueue(cache);
|
||||
}
|
||||
}
|
||||
|
||||
cache = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,13 +11,18 @@ namespace Server.Movement
|
|||
|
||||
private const TileFlag ImpassableSurface = TileFlag.Impassable | TileFlag.Surface;
|
||||
|
||||
private static Point3D m_Goal;
|
||||
private static Point3D _goal;
|
||||
|
||||
private readonly List<Mobile>[] m_MobPools = { new(), new(), new() };
|
||||
public static void Configure()
|
||||
{
|
||||
Movement.Impl = new MovementImpl();
|
||||
}
|
||||
|
||||
private readonly List<Item>[] m_Pools = { new(), new(), new(), new() };
|
||||
private readonly List<Mobile>[] _mobPools = { new(), new(), new() };
|
||||
|
||||
private readonly List<Sector> m_Sectors = new();
|
||||
private readonly List<Item>[] _pools = { new(), new(), new(), new() };
|
||||
|
||||
private readonly HashSet<Sector> _sectors = new();
|
||||
|
||||
private MovementImpl()
|
||||
{
|
||||
|
|
@ -25,12 +30,11 @@ namespace Server.Movement
|
|||
|
||||
public static bool AlwaysIgnoreDoors { get; set; }
|
||||
public static bool IgnoreMovableImpassables { get; set; }
|
||||
public static bool IgnoreSpellFields { get; set; }
|
||||
|
||||
public static Point3D Goal
|
||||
{
|
||||
get => m_Goal;
|
||||
set => m_Goal = value;
|
||||
get => _goal;
|
||||
set => _goal = value;
|
||||
}
|
||||
|
||||
public bool CheckMovement(Mobile m, Map map, Point3D loc, Direction d, out int newZ)
|
||||
|
|
@ -43,6 +47,7 @@ namespace Server.Movement
|
|||
|
||||
var xStart = loc.X;
|
||||
var yStart = loc.Y;
|
||||
|
||||
int xForward = xStart, yForward = yStart;
|
||||
int xRight = xStart, yRight = yStart;
|
||||
int xLeft = xStart, yLeft = yStart;
|
||||
|
|
@ -59,10 +64,10 @@ namespace Server.Movement
|
|||
return false;
|
||||
}
|
||||
|
||||
var itemsStart = m_Pools[0];
|
||||
var itemsForward = m_Pools[1];
|
||||
var itemsLeft = m_Pools[2];
|
||||
var itemsRight = m_Pools[3];
|
||||
var itemsStart = _pools[0];
|
||||
var itemsForward = _pools[1];
|
||||
var itemsLeft = _pools[2];
|
||||
var itemsRight = _pools[3];
|
||||
|
||||
var ignoreMovableImpassables = IgnoreMovableImpassables;
|
||||
var reqFlags = ImpassableSurface;
|
||||
|
|
@ -72,11 +77,11 @@ namespace Server.Movement
|
|||
reqFlags |= TileFlag.Wet;
|
||||
}
|
||||
|
||||
var mobsForward = m_MobPools[0];
|
||||
var mobsLeft = m_MobPools[1];
|
||||
var mobsRight = m_MobPools[2];
|
||||
var mobsForward = _mobPools[0];
|
||||
var mobsLeft = _mobPools[1];
|
||||
var mobsRight = _mobPools[2];
|
||||
|
||||
var checkMobs = (m as BaseCreature)?.Controlled == false && (xForward != m_Goal.X || yForward != m_Goal.Y);
|
||||
var checkMobs = (m as BaseCreature)?.Controlled == false && (xForward != _goal.X || yForward != _goal.Y);
|
||||
|
||||
if (checkDiagonals)
|
||||
{
|
||||
|
|
@ -85,40 +90,24 @@ namespace Server.Movement
|
|||
var sectorLeft = map.GetSector(xLeft, yLeft);
|
||||
var sectorRight = map.GetSector(xRight, yRight);
|
||||
|
||||
var sectors = m_Sectors;
|
||||
_sectors.Add(sectorStart);
|
||||
|
||||
sectors.Add(sectorStart);
|
||||
_sectors.Add(sectorForward);
|
||||
_sectors.Add(sectorLeft);
|
||||
_sectors.Add(sectorRight);
|
||||
|
||||
if (!sectors.Contains(sectorForward))
|
||||
foreach (var sector in _sectors)
|
||||
{
|
||||
sectors.Add(sectorForward);
|
||||
}
|
||||
|
||||
if (!sectors.Contains(sectorLeft))
|
||||
{
|
||||
sectors.Add(sectorLeft);
|
||||
}
|
||||
|
||||
if (!sectors.Contains(sectorRight))
|
||||
{
|
||||
sectors.Add(sectorRight);
|
||||
}
|
||||
|
||||
for (var i = 0; i < sectors.Count; ++i)
|
||||
{
|
||||
var sector = sectors[i];
|
||||
|
||||
for (var j = 0; j < sector.Items.Count; ++j)
|
||||
{
|
||||
var item = sector.Items[j];
|
||||
|
||||
if (ignoreMovableImpassables && item.Movable &&
|
||||
(item.ItemData.Flags & ImpassableSurface) != 0)
|
||||
if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((item.ItemData.Flags & reqFlags) == 0)
|
||||
if (!item.ItemData[reqFlags])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -168,29 +157,26 @@ namespace Server.Movement
|
|||
}
|
||||
}
|
||||
|
||||
if (m_Sectors.Count > 0)
|
||||
{
|
||||
m_Sectors.Clear();
|
||||
}
|
||||
_sectors.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
var sectorStart = map.GetSector(xStart, yStart);
|
||||
var sectorForward = map.GetSector(xForward, yForward);
|
||||
var sectorStartIsForward = sectorStart == sectorForward;
|
||||
|
||||
if (sectorStart == sectorForward)
|
||||
if (!sectorStartIsForward)
|
||||
{
|
||||
for (var i = 0; i < sectorStart.Items.Count; ++i)
|
||||
for (var i = 0; i < sectorForward.Items.Count; ++i)
|
||||
{
|
||||
var item = sectorStart.Items[i];
|
||||
var item = sectorForward.Items[i];
|
||||
|
||||
if (ignoreMovableImpassables && item.Movable &&
|
||||
(item.ItemData.Flags & ImpassableSurface) != 0)
|
||||
if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((item.ItemData.Flags & reqFlags) == 0)
|
||||
if (!item.ItemData[reqFlags])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -200,60 +186,39 @@ namespace Server.Movement
|
|||
continue;
|
||||
}
|
||||
|
||||
if (item.AtWorldPoint(xStart, yStart))
|
||||
{
|
||||
itemsStart.Add(item);
|
||||
}
|
||||
else if (item.AtWorldPoint(xForward, yForward))
|
||||
if (item.AtWorldPoint(xForward, yForward))
|
||||
{
|
||||
itemsForward.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
for (var i = 0; i < sectorStart.Items.Count; ++i)
|
||||
{
|
||||
for (var i = 0; i < sectorForward.Items.Count; ++i)
|
||||
var item = sectorStart.Items[i];
|
||||
|
||||
if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface)
|
||||
{
|
||||
var item = sectorForward.Items[i];
|
||||
|
||||
if (ignoreMovableImpassables && item.Movable &&
|
||||
(item.ItemData.Flags & ImpassableSurface) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((item.ItemData.Flags & reqFlags) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.AtWorldPoint(xForward, yForward) && !(item is BaseMulti) &&
|
||||
item.ItemID <= TileData.MaxItemValue)
|
||||
{
|
||||
itemsForward.Add(item);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < sectorStart.Items.Count; ++i)
|
||||
if (!item.ItemData[reqFlags])
|
||||
{
|
||||
var item = sectorStart.Items[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreMovableImpassables && item.Movable &&
|
||||
(item.ItemData.Flags & ImpassableSurface) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item is BaseMulti || item.ItemID > TileData.MaxItemValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((item.ItemData.Flags & reqFlags) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.AtWorldPoint(xStart, yStart) && !(item is BaseMulti) &&
|
||||
item.ItemID <= TileData.MaxItemValue)
|
||||
{
|
||||
itemsStart.Add(item);
|
||||
}
|
||||
if (item.AtWorldPoint(xStart, yStart))
|
||||
{
|
||||
itemsStart.Add(item);
|
||||
}
|
||||
else if (sectorStartIsForward && item.AtWorldPoint(xForward, yForward))
|
||||
{
|
||||
itemsForward.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,58 +238,22 @@ namespace Server.Movement
|
|||
|
||||
GetStartZ(m, map, loc, itemsStart, out var startZ, out var startTop);
|
||||
|
||||
var moveIsOk = Check(
|
||||
map,
|
||||
m,
|
||||
itemsForward,
|
||||
mobsForward,
|
||||
xForward,
|
||||
yForward,
|
||||
startTop,
|
||||
startZ,
|
||||
m.CanSwim,
|
||||
m.CantWalk,
|
||||
out newZ
|
||||
);
|
||||
var moveIsOk = Check(map, m, itemsForward, mobsForward, xForward, yForward, startTop, startZ, out newZ);
|
||||
|
||||
if (moveIsOk && checkDiagonals)
|
||||
{
|
||||
if (m.Player && m.AccessLevel < AccessLevel.GameMaster)
|
||||
{
|
||||
if (!Check(map, m, itemsLeft, mobsLeft, xLeft, yLeft, startTop, startZ, m.CanSwim, m.CantWalk, out _) ||
|
||||
!Check(
|
||||
map,
|
||||
m,
|
||||
itemsRight,
|
||||
mobsRight,
|
||||
xRight,
|
||||
yRight,
|
||||
startTop,
|
||||
startZ,
|
||||
m.CanSwim,
|
||||
m.CantWalk,
|
||||
out _
|
||||
))
|
||||
if (!Check(map, m, itemsLeft, mobsLeft, xLeft, yLeft, startTop, startZ, out _) ||
|
||||
!Check(map, m, itemsRight, mobsRight, xRight, yRight, startTop, startZ, out _))
|
||||
{
|
||||
moveIsOk = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Check(map, m, itemsLeft, mobsLeft, xLeft, yLeft, startTop, startZ, m.CanSwim, m.CantWalk, out _) &&
|
||||
!Check(
|
||||
map,
|
||||
m,
|
||||
itemsRight,
|
||||
mobsRight,
|
||||
xRight,
|
||||
yRight,
|
||||
startTop,
|
||||
startZ,
|
||||
m.CanSwim,
|
||||
m.CantWalk,
|
||||
out _
|
||||
))
|
||||
if (!Check(map, m, itemsLeft, mobsLeft, xLeft, yLeft, startTop, startZ, out _) &&
|
||||
!Check(map, m, itemsRight, mobsRight, xRight, yRight, startTop, startZ, out _))
|
||||
{
|
||||
moveIsOk = false;
|
||||
}
|
||||
|
|
@ -333,12 +262,12 @@ namespace Server.Movement
|
|||
|
||||
for (int i = 0, c = checkDiagonals ? 4 : 2; i < c; ++i)
|
||||
{
|
||||
m_Pools[i].Clear();
|
||||
_pools[i].Clear();
|
||||
}
|
||||
|
||||
for (int i = 0, c = checkDiagonals ? 3 : 1; i < c; ++i)
|
||||
{
|
||||
m_MobPools[i].Clear();
|
||||
_mobPools[i].Clear();
|
||||
}
|
||||
|
||||
if (!moveIsOk)
|
||||
|
|
@ -351,11 +280,6 @@ namespace Server.Movement
|
|||
|
||||
public bool CheckMovement(Mobile m, Direction d, out int newZ) => CheckMovement(m, m.Map, m.Location, d, out newZ);
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
Movement.Impl = new MovementImpl();
|
||||
}
|
||||
|
||||
private bool IsOk(
|
||||
bool ignoreDoors, bool ignoreSpellFields, int ourZ, int ourTop, StaticTile[] tiles, List<Item> items
|
||||
)
|
||||
|
|
@ -365,7 +289,7 @@ namespace Server.Movement
|
|||
var check = tiles[i];
|
||||
var itemData = TileData.ItemTable[check.ID & TileData.MaxItemValue];
|
||||
|
||||
if ((itemData.Flags & ImpassableSurface) != 0) // Impassable || Surface
|
||||
if (itemData.ImpassableSurface)
|
||||
{
|
||||
var checkZ = check.Z;
|
||||
var checkTop = checkZ + itemData.CalcHeight;
|
||||
|
|
@ -382,18 +306,15 @@ namespace Server.Movement
|
|||
var item = items[i];
|
||||
var itemID = item.ItemID & TileData.MaxItemValue;
|
||||
var itemData = TileData.ItemTable[itemID];
|
||||
var flags = itemData.Flags;
|
||||
|
||||
if ((flags & ImpassableSurface) != 0) // Impassable || Surface
|
||||
if (itemData.ImpassableSurface)
|
||||
{
|
||||
if (ignoreDoors && ((flags & TileFlag.Door) != 0 || itemID == 0x692 || itemID == 0x846 ||
|
||||
itemID == 0x873 ||
|
||||
itemID >= 0x6F5 && itemID <= 0x6F6))
|
||||
if (ignoreDoors && (itemData.Door || itemID is 0x692 or 0x846 or 0x873 || itemID >= 0x6F5 && itemID <= 0x6F6))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreSpellFields && (itemID == 0x82 || itemID == 0x3946 || itemID == 0x3956))
|
||||
if (ignoreSpellFields && itemID is 0x82 or 0x3946 or 0x3956)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -412,12 +333,21 @@ namespace Server.Movement
|
|||
}
|
||||
|
||||
private bool Check(
|
||||
Map map, Mobile m, List<Item> items, List<Mobile> mobiles, int x, int y, int startTop, int startZ,
|
||||
bool canSwim, bool cantWalk, out int newZ
|
||||
Map map,
|
||||
Mobile m,
|
||||
List<Item> items,
|
||||
List<Mobile> mobiles,
|
||||
int x,
|
||||
int y,
|
||||
int startTop,
|
||||
int startZ,
|
||||
out int newZ
|
||||
)
|
||||
{
|
||||
newZ = 0;
|
||||
|
||||
var cantWalk = m.CantWalk;
|
||||
var canSwim = m.CanSwim;
|
||||
var tiles = map.Tiles.GetStaticTiles(x, y, true);
|
||||
var landTile = map.Tiles.GetLandTile(x, y);
|
||||
var flags = TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags;
|
||||
|
|
@ -438,17 +368,45 @@ namespace Server.Movement
|
|||
var ignoreDoors = AlwaysIgnoreDoors || !m.Alive || m.Body.BodyID == 0x3DB || m.IsDeadBondedPet;
|
||||
var ignoreSpellFields = m is PlayerMobile && map != Map.Felucca;
|
||||
|
||||
int testTop;
|
||||
|
||||
for (var i = 0; i < tiles.Length; ++i)
|
||||
{
|
||||
var tile = tiles[i];
|
||||
var itemData = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
|
||||
flags = itemData.Flags;
|
||||
|
||||
var notWater = (flags & TileFlag.Wet) == 0;
|
||||
if (m.Flying && itemData.Name.InsensitiveEquals("hover over"))
|
||||
{
|
||||
newZ = tile.Z;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Surface && !Impassable
|
||||
if ((flags & ImpassableSurface) != TileFlag.Surface && (!canSwim || notWater) ||
|
||||
cantWalk && notWater)
|
||||
// Stygian Dragon
|
||||
if (m.Body == 826 && map == Map.TerMur)
|
||||
{
|
||||
if (x is >= 307 and <= 354 && y is >= 126 and <= 192)
|
||||
{
|
||||
if (tile.Z > newZ)
|
||||
{
|
||||
newZ = tile.Z;
|
||||
}
|
||||
|
||||
moveIsOk = true;
|
||||
}
|
||||
else if (x is >= 42 and <= 89 && y is >= 333 and <= 399 or >= 531 and <= 597 or >= 739 and <= 805)
|
||||
{
|
||||
if (tile.Z > newZ)
|
||||
{
|
||||
newZ = tile.Z;
|
||||
}
|
||||
|
||||
moveIsOk = true;
|
||||
}
|
||||
}
|
||||
|
||||
var notWater = !itemData.Wet;
|
||||
|
||||
if (!itemData.Surface && itemData.Impassable && (!canSwim || notWater) || cantWalk && notWater)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -456,8 +414,7 @@ namespace Server.Movement
|
|||
var itemZ = tile.Z;
|
||||
var itemTop = itemZ;
|
||||
var ourZ = itemZ + itemData.CalcHeight;
|
||||
// int ourTop = ourZ + PersonHeight;
|
||||
var testTop = checkTop;
|
||||
testTop = checkTop;
|
||||
|
||||
if (moveIsOk)
|
||||
{
|
||||
|
|
@ -479,29 +436,31 @@ namespace Server.Movement
|
|||
itemTop += itemData.Height;
|
||||
}
|
||||
|
||||
if (stepTop >= itemTop)
|
||||
if (stepTop < itemTop)
|
||||
{
|
||||
var landCheck = itemZ;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (itemData.Height >= StepHeight)
|
||||
{
|
||||
landCheck += StepHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
landCheck += itemData.Height;
|
||||
}
|
||||
var landCheck = itemZ;
|
||||
|
||||
if (considerLand && landCheck < landCenter && landCenter > ourZ && testTop > landZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (itemData.Height >= StepHeight)
|
||||
{
|
||||
landCheck += StepHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
landCheck += itemData.Height;
|
||||
}
|
||||
|
||||
if (IsOk(ignoreDoors, ignoreSpellFields, ourZ, testTop, tiles, items))
|
||||
{
|
||||
newZ = ourZ;
|
||||
moveIsOk = true;
|
||||
}
|
||||
if (considerLand && landCheck < landCenter && landCenter > ourZ && testTop > landZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsOk(ignoreDoors, ignoreSpellFields, ourZ, testTop, tiles, items))
|
||||
{
|
||||
newZ = ourZ;
|
||||
moveIsOk = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -509,14 +468,16 @@ namespace Server.Movement
|
|||
{
|
||||
var item = items[i];
|
||||
var itemData = item.ItemData;
|
||||
flags = itemData.Flags;
|
||||
|
||||
var notWater = (flags & TileFlag.Wet) == 0;
|
||||
if (m.Flying && itemData.Name.InsensitiveEquals("hover over"))
|
||||
{
|
||||
newZ = item.Z;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Surface && !Impassable && !Movable
|
||||
if (item.Movable ||
|
||||
(flags & ImpassableSurface) != TileFlag.Surface && (!m.CanSwim || notWater) ||
|
||||
cantWalk && notWater)
|
||||
var notWater = !itemData.Wet;
|
||||
|
||||
if (item.Movable || !itemData.Surface && itemData.Impassable && (!canSwim || notWater) || cantWalk && notWater)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -524,8 +485,7 @@ namespace Server.Movement
|
|||
var itemZ = item.Z;
|
||||
var itemTop = itemZ;
|
||||
var ourZ = itemZ + itemData.CalcHeight;
|
||||
// int ourTop = ourZ + PersonHeight;
|
||||
var testTop = checkTop;
|
||||
testTop = checkTop;
|
||||
|
||||
if (moveIsOk)
|
||||
{
|
||||
|
|
@ -547,62 +507,64 @@ namespace Server.Movement
|
|||
itemTop += itemData.Height;
|
||||
}
|
||||
|
||||
if (stepTop >= itemTop)
|
||||
if (stepTop < itemTop)
|
||||
{
|
||||
var landCheck = itemZ;
|
||||
|
||||
if (itemData.Height >= StepHeight)
|
||||
{
|
||||
landCheck += StepHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
landCheck += itemData.Height;
|
||||
}
|
||||
|
||||
if (considerLand && landCheck < landCenter && landCenter > ourZ && testTop > landZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsOk(ignoreDoors, ignoreSpellFields, ourZ, testTop, tiles, items))
|
||||
{
|
||||
newZ = ourZ;
|
||||
moveIsOk = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (considerLand && !landBlocks && stepTop >= landZ)
|
||||
{
|
||||
var ourZ = landCenter;
|
||||
// int ourTop = ourZ + PersonHeight;
|
||||
var testTop = checkTop;
|
||||
|
||||
if (ourZ + PersonHeight > testTop)
|
||||
{
|
||||
testTop = ourZ + PersonHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
var shouldCheck = true;
|
||||
var landCheck = itemZ;
|
||||
|
||||
if (moveIsOk)
|
||||
if (itemData.Height >= StepHeight)
|
||||
{
|
||||
var cmp = (ourZ - m.Z).Abs() - (newZ - m.Z).Abs();
|
||||
|
||||
if (cmp > 0 || cmp == 0 && ourZ > newZ)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
landCheck += StepHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
landCheck += itemData.Height;
|
||||
}
|
||||
|
||||
if (shouldCheck && IsOk(ignoreDoors, ignoreSpellFields, ourZ, testTop, tiles, items))
|
||||
if (considerLand && landCheck < landCenter && landCenter > ourZ && testTop > landZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsOk(ignoreDoors, ignoreSpellFields, ourZ, testTop, tiles, items))
|
||||
{
|
||||
newZ = ourZ;
|
||||
moveIsOk = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!considerLand || landBlocks || stepTop < landZ)
|
||||
{
|
||||
return moveIsOk;
|
||||
}
|
||||
|
||||
testTop = checkTop;
|
||||
|
||||
if (landCenter + PersonHeight > testTop)
|
||||
{
|
||||
testTop = landCenter + PersonHeight;
|
||||
}
|
||||
|
||||
var shouldCheck = true;
|
||||
|
||||
if (moveIsOk)
|
||||
{
|
||||
var cmp = (landCenter - m.Z).Abs() - (newZ - m.Z).Abs();
|
||||
|
||||
if (cmp > 0 || cmp == 0 && landCenter > newZ)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCheck && IsOk(ignoreDoors, ignoreSpellFields, landCenter, testTop, tiles, items))
|
||||
{
|
||||
newZ = landCenter;
|
||||
moveIsOk = true;
|
||||
}
|
||||
|
||||
if (moveIsOk)
|
||||
{
|
||||
for (var i = 0; moveIsOk && i < mobiles.Count; ++i)
|
||||
|
|
@ -656,59 +618,58 @@ namespace Server.Movement
|
|||
{
|
||||
var tile = staticTiles[i];
|
||||
var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
|
||||
|
||||
var calcTop = tile.Z + id.CalcHeight;
|
||||
|
||||
if ((!isSet || calcTop >= zCenter) &&
|
||||
((id.Flags & TileFlag.Surface) != 0 || m.CanSwim && (id.Flags & TileFlag.Wet) != 0) && loc.Z >= calcTop)
|
||||
if (isSet && calcTop < zCenter || loc.Z < calcTop || !id.Surface && !(m.CanSwim && id.Wet))
|
||||
{
|
||||
if (m.CantWalk && (id.Flags & TileFlag.Wet) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
zLow = tile.Z;
|
||||
zCenter = calcTop;
|
||||
|
||||
var top = tile.Z + id.Height;
|
||||
|
||||
if (!isSet || top > zTop)
|
||||
{
|
||||
zTop = top;
|
||||
}
|
||||
|
||||
isSet = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m.CantWalk && !id.Wet)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
zLow = tile.Z;
|
||||
zCenter = calcTop;
|
||||
|
||||
var top = tile.Z + id.Height;
|
||||
|
||||
if (!isSet || top > zTop)
|
||||
{
|
||||
zTop = top;
|
||||
}
|
||||
|
||||
isSet = true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < itemList.Count; ++i)
|
||||
{
|
||||
var item = itemList[i];
|
||||
|
||||
var id = item.ItemData;
|
||||
|
||||
var calcTop = item.Z + id.CalcHeight;
|
||||
|
||||
if ((!isSet || calcTop >= zCenter) &&
|
||||
((id.Flags & TileFlag.Surface) != 0 || m.CanSwim && (id.Flags & TileFlag.Wet) != 0) && loc.Z >= calcTop)
|
||||
if (isSet && calcTop < zCenter || loc.Z < calcTop || !id.Surface && !(m.CanSwim && id.Wet))
|
||||
{
|
||||
if (m.CantWalk && (id.Flags & TileFlag.Wet) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
zLow = item.Z;
|
||||
zCenter = calcTop;
|
||||
|
||||
var top = item.Z + id.Height;
|
||||
|
||||
if (!isSet || top > zTop)
|
||||
{
|
||||
zTop = top;
|
||||
}
|
||||
|
||||
isSet = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m.CantWalk && !id.Wet)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
zLow = item.Z;
|
||||
zCenter = calcTop;
|
||||
|
||||
var top = item.Z + id.Height;
|
||||
|
||||
if (!isSet || top > zTop)
|
||||
{
|
||||
zTop = top;
|
||||
}
|
||||
|
||||
isSet = true;
|
||||
}
|
||||
|
||||
if (!isSet)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Diagnostics;
|
|||
using Server.Items;
|
||||
using Server.PathAlgorithms;
|
||||
using Server.PathAlgorithms.FastAStar;
|
||||
using Server.PathAlgorithms.SlowAStar;
|
||||
using Server.Spells;
|
||||
using Server.Targeting;
|
||||
|
||||
|
|
@ -89,18 +88,21 @@ namespace Server
|
|||
var y = from.Y;
|
||||
var z = from.Z;
|
||||
|
||||
WayPoint waypoint = null;
|
||||
|
||||
for (var i = 0; i < path.Directions.Length; ++i)
|
||||
{
|
||||
Movement.Movement.Offset(path.Directions[i], ref x, ref y);
|
||||
|
||||
new RecallRune().MoveToWorld(new Point3D(x, y, z + zOffset), from.Map);
|
||||
waypoint = new WayPoint(waypoint);
|
||||
waypoint.MoveToWorld(new Point3D(x, y, z + zOffset), from.Map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Path_OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (!(targeted is IPoint3D p))
|
||||
if (targeted is not IPoint3D p)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -108,7 +110,6 @@ namespace Server
|
|||
SpellHelper.GetSurfaceTop(ref p);
|
||||
|
||||
Path(from, p, FastAStarAlgorithm.Instance, "Fast", 0);
|
||||
Path(from, p, SlowAStarAlgorithm.Instance, "Slow", 2);
|
||||
OverrideAlgorithm = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,314 +0,0 @@
|
|||
using System;
|
||||
using Server.Mobiles;
|
||||
using CalcMoves = Server.Movement.Movement;
|
||||
using MoveImpl = Server.Movement.MovementImpl;
|
||||
|
||||
namespace Server.PathAlgorithms.SlowAStar
|
||||
{
|
||||
public struct PathNode
|
||||
{
|
||||
public int x, y, z;
|
||||
public int g, h;
|
||||
public int px, py, pz;
|
||||
public int dir;
|
||||
}
|
||||
|
||||
public class SlowAStarAlgorithm : PathAlgorithm
|
||||
{
|
||||
private const int MaxDepth = 300;
|
||||
private const int MaxNodes = MaxDepth * 16;
|
||||
public static PathAlgorithm Instance = new SlowAStarAlgorithm();
|
||||
|
||||
private static readonly PathNode[] m_Closed = new PathNode[MaxNodes];
|
||||
private static readonly PathNode[] m_Open = new PathNode[MaxNodes];
|
||||
private static readonly PathNode[] m_Successors = new PathNode[8];
|
||||
private static readonly Direction[] m_Path = new Direction[MaxNodes];
|
||||
|
||||
private Point3D m_Goal;
|
||||
|
||||
public int Heuristic(int x, int y, int z)
|
||||
{
|
||||
x -= m_Goal.X;
|
||||
y -= m_Goal.Y;
|
||||
z -= m_Goal.Z;
|
||||
|
||||
x *= 11;
|
||||
y *= 11;
|
||||
|
||||
return x * x + y * y + z * z;
|
||||
}
|
||||
|
||||
public override bool CheckCondition(Mobile m, Map map, Point3D start, Point3D goal) => false;
|
||||
|
||||
public override Direction[] Find(Mobile m, Map map, Point3D start, Point3D goal)
|
||||
{
|
||||
m_Goal = goal;
|
||||
|
||||
var bc = m as BaseCreature;
|
||||
|
||||
PathNode curNode;
|
||||
|
||||
var goalNode = new PathNode();
|
||||
goalNode.x = goal.X;
|
||||
goalNode.y = goal.Y;
|
||||
goalNode.z = goal.Z;
|
||||
|
||||
var startNode = new PathNode();
|
||||
startNode.x = start.X;
|
||||
startNode.y = start.Y;
|
||||
startNode.z = start.Z;
|
||||
startNode.h = Heuristic(startNode.x, startNode.y, startNode.z);
|
||||
|
||||
PathNode[] closed = m_Closed, open = m_Open, successors = m_Successors;
|
||||
var path = m_Path;
|
||||
|
||||
int closedCount = 0, openCount = 0;
|
||||
var pathCount = 0;
|
||||
var depth = 0;
|
||||
|
||||
var iBacktrack = 0;
|
||||
|
||||
open[openCount++] = startNode;
|
||||
|
||||
while (openCount > 0)
|
||||
{
|
||||
curNode = open[0];
|
||||
var curF = curNode.g + curNode.h;
|
||||
var popIndex = 0;
|
||||
|
||||
for (var i = 1; i < openCount; ++i)
|
||||
{
|
||||
if (open[i].g + open[i].h < curF)
|
||||
{
|
||||
curNode = open[i];
|
||||
curF = curNode.g + curNode.h;
|
||||
popIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (curNode.x == goalNode.x && curNode.y == goalNode.y && (curNode.z - goalNode.z).Abs() < 16)
|
||||
{
|
||||
if (closedCount == MaxNodes)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
closed[closedCount++] = curNode;
|
||||
|
||||
var xBacktrack = curNode.px;
|
||||
var yBacktrack = curNode.py;
|
||||
var zBacktrack = curNode.pz;
|
||||
|
||||
path[pathCount++] = (Direction)curNode.dir;
|
||||
|
||||
// if (pathCount == MaxNodes)
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
|
||||
while (xBacktrack != startNode.x || yBacktrack != startNode.y || zBacktrack != startNode.z)
|
||||
{
|
||||
var found = false;
|
||||
|
||||
for (var j = 0; !found && j < closedCount; ++j)
|
||||
{
|
||||
if (closed[j].x == xBacktrack && closed[j].y == yBacktrack && closed[j].z == zBacktrack)
|
||||
{
|
||||
if (pathCount == MaxNodes)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
curNode = closed[j];
|
||||
path[pathCount++] = (Direction)curNode.dir;
|
||||
xBacktrack = curNode.px;
|
||||
yBacktrack = curNode.py;
|
||||
zBacktrack = curNode.pz;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
Console.WriteLine("bugaboo..");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pathCount == MaxNodes)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pathCount == MaxNodes)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var dirs = new Direction[pathCount];
|
||||
|
||||
while (pathCount > 0)
|
||||
{
|
||||
dirs[iBacktrack++] = path[--pathCount];
|
||||
}
|
||||
|
||||
return dirs;
|
||||
}
|
||||
|
||||
--openCount;
|
||||
|
||||
for (var i = popIndex; i < openCount; ++i)
|
||||
{
|
||||
open[i] = open[i + 1];
|
||||
}
|
||||
|
||||
var sucCount = 0;
|
||||
|
||||
if (bc != null)
|
||||
{
|
||||
MoveImpl.AlwaysIgnoreDoors = bc.CanOpenDoors;
|
||||
MoveImpl.IgnoreMovableImpassables = bc.CanMoveOverObstacles;
|
||||
}
|
||||
|
||||
MoveImpl.Goal = goal;
|
||||
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
for (var i = 0; i < 8; ++i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
default:
|
||||
x = 0;
|
||||
y = -1;
|
||||
break;
|
||||
case 1:
|
||||
x = 1;
|
||||
y = -1;
|
||||
break;
|
||||
case 2:
|
||||
x = 1;
|
||||
y = 0;
|
||||
break;
|
||||
case 3:
|
||||
x = 1;
|
||||
y = 1;
|
||||
break;
|
||||
case 4:
|
||||
x = 0;
|
||||
y = 1;
|
||||
break;
|
||||
case 5:
|
||||
x = -1;
|
||||
y = 1;
|
||||
break;
|
||||
case 6:
|
||||
x = -1;
|
||||
y = 0;
|
||||
break;
|
||||
case 7:
|
||||
x = -1;
|
||||
y = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (CalcMoves.CheckMovement(m, map, new Point3D(curNode.x, curNode.y, curNode.z), (Direction)i, out z))
|
||||
{
|
||||
successors[sucCount].x = x + curNode.x;
|
||||
successors[sucCount].y = y + curNode.y;
|
||||
successors[sucCount++].z = z;
|
||||
}
|
||||
}
|
||||
|
||||
MoveImpl.AlwaysIgnoreDoors = false;
|
||||
MoveImpl.IgnoreMovableImpassables = false;
|
||||
MoveImpl.Goal = Point3D.Zero;
|
||||
|
||||
if (sucCount == 0 || ++depth > MaxDepth)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (var i = 0; i < sucCount; ++i)
|
||||
{
|
||||
x = successors[i].x;
|
||||
y = successors[i].y;
|
||||
z = successors[i].z;
|
||||
|
||||
successors[i].g = curNode.g + 1;
|
||||
|
||||
int openIndex = -1, closedIndex = -1;
|
||||
|
||||
for (var j = 0; openIndex == -1 && j < openCount; ++j)
|
||||
{
|
||||
if (open[j].x == x && open[j].y == y && open[j].z == z)
|
||||
{
|
||||
openIndex = j;
|
||||
}
|
||||
}
|
||||
|
||||
if (openIndex >= 0 && open[openIndex].g < successors[i].g)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var j = 0; closedIndex == -1 && j < closedCount; ++j)
|
||||
{
|
||||
if (closed[j].x == x && closed[j].y == y && closed[j].z == z)
|
||||
{
|
||||
closedIndex = j;
|
||||
}
|
||||
}
|
||||
|
||||
if (closedIndex >= 0 && closed[closedIndex].g < successors[i].g)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (openIndex >= 0)
|
||||
{
|
||||
--openCount;
|
||||
|
||||
for (var j = openIndex; j < openCount; ++j)
|
||||
{
|
||||
open[j] = open[j + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (closedIndex >= 0)
|
||||
{
|
||||
--closedCount;
|
||||
|
||||
for (var j = closedIndex; j < closedCount; ++j)
|
||||
{
|
||||
closed[j] = closed[j + 1];
|
||||
}
|
||||
}
|
||||
|
||||
successors[i].px = curNode.x;
|
||||
successors[i].py = curNode.y;
|
||||
successors[i].pz = curNode.z;
|
||||
successors[i].dir = (int)GetDirection(curNode.x, curNode.y, x, y);
|
||||
successors[i].h = Heuristic(x, y, z);
|
||||
|
||||
if (openCount == MaxNodes)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
open[openCount++] = successors[i];
|
||||
}
|
||||
|
||||
if (openCount == MaxNodes || closedCount == MaxNodes)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
closed[closedCount++] = curNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ namespace Server.Engines.Spawners
|
|||
{
|
||||
var stemDir = stem[..^file.Name.Length];
|
||||
var fullOutputDir = Path.Combine(outputDir, stemDir);
|
||||
AssemblyHandler.EnsureDirectory(fullOutputDir);
|
||||
PathUtility.EnsureDirectory(fullOutputDir);
|
||||
|
||||
var outputFileName = $"{file.Name[..^file.Extension.Length]}.json";
|
||||
ParsePremiumSpawnerFile(file.FullName, Path.Combine(fullOutputDir, outputFileName), options);
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ namespace Server.Engines.Spawners
|
|||
if (!Path.IsPathRooted(path))
|
||||
{
|
||||
path = Path.Combine(Core.BaseDirectory, path);
|
||||
AssemblyHandler.EnsureDirectory(directory);
|
||||
PathUtility.EnsureDirectory(directory);
|
||||
}
|
||||
else if (!Directory.Exists(directory))
|
||||
{
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue