Merge branch 'main' into behavior-tree

# Conflicts:
#	Projects/UOContent/UOContent.csproj
This commit is contained in:
Kamron Batman 2021-10-16 12:16:18 -07:00
commit 380f40890c
No known key found for this signature in database
GPG key ID: 5C9DFD15804B6BB8
385 changed files with 10308 additions and 6956 deletions

View file

@ -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

View file

@ -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
View file

@ -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

View file

@ -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>

View file

@ -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

View 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

View 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

View 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

View 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

View 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

View 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
}
}
]

File diff suppressed because it is too large Load diff

12
FAQ.md
View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -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
);
}
}

View file

@ -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
);
}
}

View file

@ -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)

View file

@ -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" />

View 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>

View file

@ -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

View file

@ -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)
);
}
}

View file

@ -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>

View file

@ -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();

View file

@ -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

View 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;
}
}

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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
{

View file

@ -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; }
}
}

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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()

View 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());
}
}

View file

@ -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();
}
}

View 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
};
}
}

View file

@ -14,7 +14,6 @@
*************************************************************************/
using System;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;

View file

@ -14,7 +14,6 @@
*************************************************************************/
using System;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;

View file

@ -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());

View file

@ -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);
}
}
}
}

View file

@ -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;

View file

@ -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)

View file

@ -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();

View file

@ -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
View 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;

View file

@ -125,7 +125,6 @@ namespace Server.Network
return outputIdx;
}
public static int Compress(CircularBuffer<byte> input, CircularBuffer<byte> output)
{
if (input.Length > DefiniteOverflow)

View file

@ -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();
}
}

View file

@ -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,

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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)
{

View file

@ -35,7 +35,6 @@ namespace Server
public override long Position => _position + Index;
protected override int BufferSize => 81920;
public override void Flush()

View file

@ -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())

View file

@ -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");

View file

@ -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();

View file

@ -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)
{

View file

@ -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();
}
}
}

View file

@ -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)
{

View file

@ -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>

View file

@ -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;

View file

@ -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)

View file

@ -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)]

View 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);
}
}

View 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; }
}
}

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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()
{
}
}
}

View file

@ -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;
}

View 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()));
}
}
}

View file

@ -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;
}
}
}

View file

@ -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
{

View file

@ -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

View file

@ -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

View file

@ -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" />

View 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);
}
}
}

View file

@ -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>

View file

@ -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;
}

View file

@ -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);

View file

@ -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)
{

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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)

View 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;
}
}
}

View 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);
}
}
}

View file

@ -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;

View file

@ -410,7 +410,6 @@ namespace Server.Engines.CannedEvil
}
}
// Justice reward
var pm = (PlayerMobile)killer;
for (var j = 0; j < pm.JusticeProtectors.Count; ++j)

View file

@ -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)
{

View file

@ -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;

View file

@ -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%
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}
}

View file

@ -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)

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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);

View file

@ -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