Merge pull request #212 from blizzless/community

Community
This commit is contained in:
Enthusiast 2025-09-29 11:27:36 -03:00 committed by GitHub
commit c530ee679e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
104 changed files with 5229 additions and 1593 deletions

8
.gitignore vendored
View File

@ -370,3 +370,11 @@ FodyWeavers.xsd
db/data/pgdata/
db/pgadmin/*
!db/pgadmin/pgpass
/src/DiIiS-NA/config.ini
/src/DiIiS-NA/database.Account.config
/src/DiIiS-NA/database.Worlds.config
/src/DiIiS-NA/database.Account.Debug.config
/src/DiIiS-NA/database.Worlds.Debug.config
# Generated config.mods.json
config.mods.json

24
Dockerfile Normal file
View File

@ -0,0 +1,24 @@
# Use the official .NET SDK image to build the application
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /app
# Copy the project file and restore dependencies
COPY ["src/DiIiS-NA/Blizzless.csproj", "src/DiIiS-NA/"]
RUN dotnet restore "src/DiIiS-NA/Blizzless.csproj"
# Copy the rest of the project files and build the application
COPY ["src/", "src/"]
WORKDIR "/app/src/DiIiS-NA"
RUN dotnet publish "Blizzless.csproj" -c Release --runtime linux-x64 --self-contained true -o /app/publish
# Use the official .NET runtime image to run the application
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS runtime
WORKDIR /app
# Copy the published application from the build stage
COPY --from=build /app/publish .
# Expose the port your application is running on (if needed)
EXPOSE 1345 1119 83 2001 9800 9100
# Start the application
ENTRYPOINT ["./Blizzless"]

View File

@ -6,6 +6,14 @@
DiIiS is a fully-functional open-source local server for [Diablo III: Reaper of Souls](https://eu.diablo3.blizzard.com).
# Development
Developers, please contribute using the branch: **_community_**, always create your branches from it.
# Enthusiasts
To test the server, use the **_test-stable_** or **_community_** branch
## Features
- Implemented account creation system, authorization and lobby.
@ -51,15 +59,20 @@ The currently supported version of the client: **2.7.4.84161**
### Compile and run
1. Install [.NET 7 SDK and runtime](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) (just runtime, not asp.net or desktop)
2. Go to the repo directory and compile the project using this command:
```shell
dotnet publish ./src/DiIiS-NA/Blizzless.csproj --configuration Release --output ./publish
```
3. [Skip this stage for local game] Copy the [config.ini](configs/config.ini) file to the publish folder (It overwrites the default settings):
- Update the parameter entries with your IP record on the network: `BindIP` and `PublicIP`.
4. Go to the publish folder, launch Blizzless executable, wait until server start - it creates a hierarchy.
5. Create user account(s) using console: `!account add Login Password Tag`
```shell
dotnet publish ./src/DiIiS-NA/Blizzless.csproj --configuration Release --output ./publish
```
3. __Skip this stage for local game__ Copy the [config.mods.json](https://github.com/blizzless/blizzless-diiis/blob/community/configs/config.mods.json) file to the folder, and modify however you want. A file will be generated automatically from the `config.ini` for now.
4. Update your `config.ini` file on the published folder with your network's IP records (`BindIP` and `PublicIP`)
5. Go to the publish folder, launch Blizzless executable, wait until server start - it creates a hierarchy.
6. Create user account(s) using console: `!account add Login Password Tag`
- Example:
- `!account add username@ YourPassword YourBattleTag`
- Creates an account with Login `username@`, password `YourPassword` and BattleTag `YourBattleTag`
- `!account add username@ YourPassword YourBattleTag owner`
- Creates an account with Login `username@`, password `YourPassword` and BattleTag `YourBattleTag` with rank `owner`
#### Example:
### Example:
> !account add username@ YourPassword YourBattleTag
@ -129,6 +142,51 @@ The command system allows you to get control of the game world if you have right
Check the [report form](docs/report-form.md) before submitting issue, this will help people save time!
# Development Roadmap Diablo-like Server/Client
### 1⃣ Rift / Greater Rift System
GR closure: ensure the Greater Rift closes automatically after 15 minutes.
Exit portal: fix the teleport at the end of the GR so it appears and works correctly.
Resource consumption: make sure the GR Stone and gold are properly consumed when energizing the rift.
Death time penalty: implement the correct penalty (+5 seconds per death after the 3rd inside the GR).
Mob removal: clear all enemies after a GR is finished.
Next-level portal display: ensure the portal to the next GR level only appears when appropriate.
Level display bug: fix the issue that shows only GR level 13 Torment 2.
GR pillars validation: review and validate the behavior of GR pillars.
### 2⃣ Items & Crafting
Kanais Cube: fix legendary power extraction so it correctly consumes the required items.
Enchant NPC: enable proper re-rolling of any items stats.
Ramaladnis Gift: ensure it correctly adds a socket to weapons.
Gem validation: review the behavior of normal and legendary gems.
Affix validation: confirm that all item affixes work as intended.
Set bonus validation: review and adjust item set bonuses.
Legendary bonus validation: ensure legendary powers are correctly applied (including duplicate checks).
Boss drop validation: confirm that bosses drop the correct items.
### 3⃣ Progression & Gameplay
Waypoints:
Fix client crash when changing Sanctuary view with the + and buttons.
Correctly display the map name when clicked.
Ensure teleporting through waypoints works reliably.
Normal rift teleport: fix the portal that remains after a normal rift is closed.
Character weapon display: fix the bug where the characters weapon is not shown.
Difficulty save: store the last difficulty level used by the player for the next session.
NPC upgrade after level 10: validate that the NPC correctly consumes Deaths Breath when upgrading.
### 4⃣ Bounties & Rewards
Bounty system: validate the full bounty system and its rewards.
### Suggested Priorities
Critical stability: GR closure, resource consumption, exit teleport, and waypoint fixes.
Player progression: NPC upgrades, Kanais Cube, Ramaladnis Gift, and difficulty saving.
Balance & content: validations for gems, affixes, set bonuses, legendary bonuses, and boss drops.
General improvements: visual fixes (weapon display) and overall gameplay polish.
# System requirements
| | **Entry-level** | **Mid-range** | **High-end** |
@ -141,5 +199,12 @@ Check the [report form](docs/report-form.md) before submitting issue, this will
You can see more screenshots [here](SCREENSHOTS.md)
![](pictures/ingame-screen-1.png)
![](pictures/d30.PNG)
![](pictures/d31.PNG)
![](pictures/d32.PNG)
![](pictures/d33.PNG)
![](pictures/d34.PNG)
![](pictures/d35.PNG)
![](pictures/d36.PNG)
![](pictures/d37.PNG)
![](pictures/d38.PNG)

View File

@ -0,0 +1,92 @@
using DiIiS_NA.Core.Logging;
using DiIiS_NA.GameServer.GSSystem.ActorSystem;
using DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations.Hirelings;
using DiIiS_NA.GameServer.GSSystem.GameSystem;
using DiIiS_NA.GameServer.GSSystem.PlayerSystem;
using DiIiS_NA.GameServer.MessageSystem;
using System.Linq;
using System;
using System.Collections.Generic;
using DiIiS_NA.LoginServer.AccountsSystem;
using DiIiS_NA.GameServer.GSSystem.QuestSystem.QuestEvents;
using DiIiS_NA.GameServer.Core.Types.Math;
using DiIiS_NA.Core.Helpers.Math;
using DiIiS_NA.GameServer.Core.Types.TagMap;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Animation;
using System.Threading.Tasks;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Base;
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
namespace DiIiS_NA.GameServer.GSSystem.QuestSystem.QuestEvents.Implementations
{
class SpawnSkeletons : QuestEvent
{
//ActorID: 0x7A3100DD
//ZombieSkinny_A_LeahInn.acr (2050031837)
//ActorSNOId: 0x00031971:ZombieSkinny_A_LeahInn.acr
private static readonly Logger Logger = LogManager.CreateLogger();
public SpawnSkeletons()
: base(151124)
{
}
public override void Execute(MapSystem.World world)
{
if (world.Game.Empty) return;
//Logger.Debug("SpawnSkeletons event started");
Task.Delay(1000).ContinueWith(delegate
{
foreach (var plr in world.Game.Players.Values)
plr.InGameClient.SendMessage(new MessageSystem.Message.Definitions.Camera.CameraCriptedSequenceStartMessage() { Activate = true });
var SkeletonKing_Bridge = world.GetActorBySNO(ActorSno._trdun_skeletonking_bridge_active);
Task.Delay(1000).ContinueWith(delegate
{
foreach (var plr in world.Game.Players.Values)
plr.InGameClient.SendMessage(new MessageSystem.Message.Definitions.Camera.CameraFocusMessage() { ActorID = (int)SkeletonKing_Bridge.DynamicID(plr), Duration = 1f, Snap = false });
StartConversation(world, 17923);
SkeletonKing_Bridge.PlayAnimation(5, (AnimationSno)SkeletonKing_Bridge.AnimationSet.TagMapAnimDefault[AnimationSetKeys.Opening], 1f);
world.BroadcastIfRevealed(plr => new SetIdleAnimationMessage
{
ActorID = SkeletonKing_Bridge.DynamicID(plr),
AnimationSNO = AnimationSetKeys.Open.ID,
}, SkeletonKing_Bridge);
Task.Delay(3000).ContinueWith(delegate
{
foreach (var plr in world.Game.Players.Values)
{
plr.InGameClient.SendMessage(new BoolDataMessage(Opcodes.CameraTriggerFadeToBlackMessage) { Field0 = true });
plr.InGameClient.SendMessage(new SimpleMessage(Opcodes.CameraSriptedSequenceStopMessage) { });
}
});
});
});
var spawner = world.GetActorBySNO(ActorSno._trdun_rescuecainskelspawner);
while (spawner != null)
{
var monster = ActorSno._skeletonking_shield_skeleton;
world.SpawnMonster(monster, spawner.Position);
spawner.Destroy();
spawner = world.GetActorBySNO(ActorSno._trdun_rescuecainskelspawner);
}
}
private bool StartConversation(MapSystem.World world, Int32 conversationId)
{
foreach (var player in world.Players)
{
player.Value.Conversations.StartConversation(conversationId);
}
return true;
}
}
}

View File

@ -1,114 +0,0 @@
;
; # This is a template configuration file which can be modified as desired.
;
; # Community branch (recommended): https://github.com/blizzless/blizzless-diiis/tree/community
; # test-stable branch: https://github.com/blizzless/blizzless-diiis/
; # Master branch: https://github.com/blizzless/blizzless-diiis/tree/master
;
; Settings for Bnet
[Battle-Server]
Enabled = true
BindIP = 127.0.0.1
WebPort = 9800
Port = 1119
MotdEnabled = true
Motd = Welcome to Blizzless D3!
; ------------------------
; [IWServer]
; IWServer = false
; ------------------------
; REST services for login (and others)
[REST]
IP = 127.0.0.1
Public = true
PublicIP = 127.0.0.1
PORT = 80
; ------------------------
; Game server options and game-mods.
;
[Game-Server]
Enabled = true
CoreActive = true
BindIP = 127.0.0.1
WebPort = 9001
Port = 1345
BindIPv6 = ::1
DRLGemu = true
; Modding of game (please check https://github.com/blizzless/blizzless-diiis/blob/community/docs/game-world-settings.md)
;
; rates
RateExp = 1
RateMoney = 1
RateDrop = 1
RateChangeDrop = 1
RateMonsterHP = 1
RateMonsterDMG = 1
; items
ChanceHighQualityUnidentified = 30
ChanceNormalUnidentified = 5
; bosses
BossHealthMultiplier = 6
BossDamageMultiplier = 3
; nephalem
NephalemRiftProgressMultiplier = 1
; health
HealthPotionRestorePercentage = 60
HealthPotionCooldown = 30
ResurrectionCharges = 3
; waypoints
UnlockAllWaypoints = false
; player attribute modifier
StrengthMultiplier = 1
StrengthParagonMultiplier = 1
DexterityMultiplier = 1
DexterityParagonMultiplier = 1
IntelligenceMultiplier = 1
IntelligenceParagonMultiplier = 1
VitalityMultiplier = 1
VitalityParagonMultiplier = 1
; quests
AutoSaveQuests = false
; minimap
ForceMinimapVisibility = false
; ------------------------
; Network address translation
;
[NAT]
Enabled = True
; use your public IP
PublicIP = 127.0.0.1
; ------------------------
; Where the outputs should be.
; Best for visualization (default): AnsiLog (target: Ansi)
; Best for debugging: ConsoleLog (target: console)
; Best for packet analysis: PacketLog (target: file)
;
[AnsiLog]
Enabled = true
Target = Ansi
IncludeTimeStamps = true
MinimumLevel = Debug
MaximumLevel = Fatal
[ConsoleLog]
Enabled = false
Target = Console
IncludeTimeStamps = true
MinimumLevel = Debug
MaximumLevel = PacketDump
[PacketLog]
Enabled = true
Target = file
FileName = packet.log
IncludeTimeStamps = true
MinimumLevel = Debug
MaximumLevel = PacketDump

60
configs/config.mods.json Normal file
View File

@ -0,0 +1,60 @@
{
"Rate": {
"Experience": 1.0,
"Money": 1.0,
"Drop": 1.0,
"ChangeDrop": 1.0
},
"Health": {
"PotionRestorePercentage": 60.0,
"PotionCooldown": 30.0,
"ResurrectionCharges": 3
},
"Monster": {
"HealthMultiplier": 1.0,
"DamageMultiplier": 1.0
},
"Boss": {
"HealthMultiplier": 6.0,
"DamageMultiplier": 3.0
},
"Quest": {
"AutoSave": false,
"UnlockAllWaypoints": false
},
"Player": {
"Multipliers": {
"Strength": {
"Normal": 1.0,
"Paragon": 1.0
},
"Dexterity": {
"Normal": 1.0,
"Paragon": 1.0
},
"Intelligence": {
"Normal": 1.0,
"Paragon": 1.0
},
"Vitality": {
"Normal": 1.0,
"Paragon": 1.0
}
}
},
"Items": {
"UnidentifiedDropChances": {
"HighQuality": 30.0,
"NormalQuality": 5.0
}
},
"Minimap": {
"ForceVisibility": false
},
"NephalemRift": {
"ProgressMultiplier": 1.0,
"AutoFinish": false,
"AutoFinishThreshold": 2,
"OrbsChance": 2.0
}
}

View File

@ -1,50 +1,40 @@
version: "3.9"
services:
postgres:
container_name: postgres_container
image: postgres:14
environment:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "postgres"
PGDATA: "/var/lib/postgresql/data/pgdata"
diiis-na-server:
build:
context: .
dockerfile: Dockerfile
container_name: diiis-na-server
volumes:
- ./data:/var/lib/postgresql/data
- ./initdb:/docker-entrypoint-initdb.d
- ./src/DiIiS-NA/config.ini:/app/config.ini
ports:
- "5432:5432"
networks:
- postgres
restart: unless-stopped
deploy:
resources:
limits:
cpus: "1"
memory: 4G
- 83:83
- 1119:1119
- 1345:1345
- 2001:2001
- 9800:9800
- 9100:9100
pgadmin:
container_name: pgadmin_container
image: dpage/pgadmin4:6
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
container_name: diiis-na-db
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-password}
PGADMIN_CONFIG_SERVER_MODE: "False"
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
volumes:
- ./pgadmin:/var/lib/pgadmin
- ./servers.json:/pgadmin4/servers.json
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=diablo
ports:
- "5555:80"
networks:
- postgres
restart: unless-stopped
deploy:
resources:
limits:
cpus: "0.5"
memory: 1G
- 5432:5432
volumes:
- db-data:/var/lib/postgresql/data
- ./db/initdb:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
postgres:
driver: bridge
volumes:
db-data:

50
db_old/docker-compose.yml Normal file
View File

@ -0,0 +1,50 @@
version: "3.9"
services:
postgres:
container_name: postgres_container
image: postgres:14
environment:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "postgres"
PGDATA: "/var/lib/postgresql/data/pgdata"
volumes:
- ./data:/var/lib/postgresql/data
- ./initdb:/docker-entrypoint-initdb.d
ports:
- "5432:5432"
networks:
- postgres
restart: unless-stopped
deploy:
resources:
limits:
cpus: "1"
memory: 4G
pgadmin:
container_name: pgadmin_container
image: dpage/pgadmin4:6
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-password}
PGADMIN_CONFIG_SERVER_MODE: "False"
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
volumes:
- ./pgadmin:/var/lib/pgadmin
- ./servers.json:/pgadmin4/servers.json
ports:
- "5555:80"
networks:
- postgres
restart: unless-stopped
deploy:
resources:
limits:
cpus: "0.5"
memory: 1G
networks:
postgres:
driver: bridge

2423
db_old/initdb/dump.sql Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,81 +1,76 @@
# Game World Settings
The parameters of the world can be easily altered using the configuration file located within `config.ini`.
The parameters of the world can be easily altered using the configuration file located within `config.maps.json`, which is built on server initialization.
For older configs, it will be migrated from `config.ini` automatically.
## Configuration
The parameters specified in the `config.ini` file will be saved to the server folder, overwriting the default settings. For example, all values below use their default settings.
The parameters specified in the `config.mods.json` file will be created on the server folder, migrating from config.ini, to overwrite the default settings. For example, all values below use their default settings.
```ini
[Game-Server]
; rates
RateExp = 1
RateMoney = 1
RateDrop = 1
RateChangeDrop = 1
RateMonsterHP = 1
RateMonsterDMG = 1
; items
ChanceHighQualityUnidentified = 30
ChanceNormalUnidentified = 5
; bosses
BossHealthMultiplier = 6
BossDamageMultiplier = 3
; nephalem
NephalemRiftProgressMultiplier = 1
NephalemRiftAutoFinish = false
NephalemRiftAutoFinishThreshold = 2
NephalemRiftOrbsChance = 0
; health
HealthPotionRestorePercentage = 60
HealthPotionCooldown = 30
ResurrectionCharges = 3
; waypoints
UnlockAllWaypoints = false
; player attribute modifier
StrengthMultiplier = 1
StrengthParagonMultiplier = 1
DexterityMultiplier = 1
DexterityParagonMultiplier = 1
IntelligenceMultiplier = 1
IntelligenceParagonMultiplier = 1
VitalityMultiplier = 1
VitalityParagonMultiplier = 1
; quests
AutoSaveQuests = false
; minimap
ForceMinimapVisibility = false
```
The default configuration can be found at [config.mods.json](https://github.com/blizzless/blizzless-diiis/blob/community/configs/config.mods.json)
## Description
| Key | Description |
| ---------------- | ------------------------- |
| `RateExp` | Experience multiplier |
| `RateMoney` | Currency multiplier |
| `RateDrop` | Drop quantity multiplier |
| `RateChangeDrop` | Drop quality multiplier |
| `RateMonsterHP` | Monsters HP multiplier |
| `RateMonsterDMG` | Monster damage multiplier |
| `ChanceHighQualityUnidentified` | Percentage that a unique, legendary, set or special item created is unidentified |
| `ChanceNormalUnidentified` | Percentage that normal item created is unidentified |
| `ResurrectionCharges` | Amount of times user can resurrect at corpse |
| `BossHealthMultiplier` | Boss Health Multiplier |
| `BossDamageMultiplier` | Boss Damage Multiplier |
| `HealthPotionRestorePercentage` | How much (from 1-100) a health potion will heal. |
| `HealthPotionCooldown` | How much (in seconds) to use a health potion again. |
| `UnlockAllWaypoints` | Unlocks all waypoints in campaign |
| `StrengthMultiplier` | Player's strength multiplier |
| `StrengthParagonMultiplier` | Player's strength multiplier **for paragons** |
| `DexterityMultiplier` | Player's dexterity multiplier |
| `DexterityParagonMultiplier` | Player's dexterity multiplier **for paragons** |
| `IntelligenceMultiplier` | Player's intelligence multiplier |
| `IntelligenceParagonMultiplier` | Player's intelligence multiplier **for paragons** |
| `VitalityMultiplier` | Player's vitality multiplier |
| `VitalityParagonMultiplier` | Player's vitality multiplier **for paragons** |
| `AutoSaveQuests` *in tests* | Force Save Quests/Step, even if Act's quest setup marked as Saveable = FALSE. Doesn't apply to OpenWorld games. |
| `NephalemRiftProgressMultiplier` | Nephalem Rift Progress Modifier |
| `NephalemRiftAutoFinish` | Nephalem Auto-Finish when there's still `NephalemRiftAutoFinishThreshold` monsters or less are alive on the rift |
| `NephalemRiftAutoFinishThreshold` | Nephalem Rift Progress Modifier |
| `NephalemRiftOrbsChance` | Nephalem Rifts chance of spawning a orb. |
| `ForceMinimapVisibility` | Forces the minimap visibility |
```json
{
"Rate": {
"Experience": 1.0, // Experience Rate
"Money": 1.0, // money rate
"Drop": 1.0, // drop rate
"ChangeDrop": 1.0 // change drop rate
},
"Health": {
"PotionRestorePercentage": 60.0, // how many in percent will a potion restore
"PotionCooldown": 30.0, // how many seconds for a full potion recharge
"ResurrectionCharges": 3 // how many times can you revive at corpse
},
"Monster": {
"HealthMultiplier": 1.0, // monster health multiplier
"DamageMultiplier": 1.0 // monster damage multiplier
},
"Boss": {
"HealthMultiplier": 6.0, // boss health multiplier
"DamageMultiplier": 3.0 // boss damage multiplier
},
"Quest": {
"AutoSave": false, // auto save at every quest
"UnlockAllWaypoints": false // unlocks all waypoints in-game
},
"Player": {
"Multipliers": { // multipliers for the player (e.g. a paragon might need twice these values for fairer gameplay)
"Strength": {
"Normal": 1.0,
"Paragon": 1.0
},
"Dexterity": {
"Normal": 1.0,
"Paragon": 1.0
},
"Intelligence": {
"Normal": 1.0,
"Paragon": 1.0
},
"Vitality": {
"Normal": 1.0,
"Paragon": 1.0
}
}
},
"Items": {
"UnidentifiedDropChances": { // chances in % of a dropped item to be unidentified
"HighQuality": 30.0,
"NormalQuality": 5.0
}
},
"Minimap": {
"ForceVisibility": false // forces in-game minimap to be always visible
},
"NephalemRift": { // improves overall nephalem rift experience
"ProgressMultiplier": 1.0,
"AutoFinish": false,
"AutoFinishThreshold": 2,
"OrbsChance": 2.0 // chances of spawning an orb
}
}
```

BIN
pictures/b3.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
pictures/d30.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
pictures/d32.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
pictures/d33.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
pictures/d34.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
pictures/d35.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
pictures/d36.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
pictures/d37.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
pictures/d38.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -37,7 +37,7 @@ namespace DiIiS_NA.LoginServer.AccountsSystem
{
ByteStringPresenceField<D3.OnlineService.EntityId> val = null;
if (GameAccount.CurrentToon != null)
val = new ByteStringPresenceField<D3.OnlineService.EntityId>(FieldKeyHelper.Program.D3, FieldKeyHelper.OriginatingClass.Account, 1, 0, GameAccount.CurrentToon.D3EntityID);
val = new ByteStringPresenceField<D3.OnlineService.EntityId>(FieldKeyHelper.Program.D3, FieldKeyHelper.OriginatingClass.Account, 1, 0, GameAccount.CurrentToon.D3EntityId);
else
{
var Fake = D3.OnlineService.EntityId.CreateBuilder().SetIdHigh(0).SetIdLow(0);

View File

@ -90,8 +90,8 @@ public class GameAccount : PersistentRPCObject
public EntityId LastPlayedHeroId =>
CurrentToon == null
? Toons.Count > 0 ? Toons.First().D3EntityID : AccountHasNoToons
: CurrentToon.D3EntityID;
? Toons.Count > 0 ? Toons.First().D3EntityId : AccountHasNoToons
: CurrentToon.D3EntityId;
public ByteStringPresenceField<bgs.protocol.channel.v1.ChannelId> PartyIdField =>
new(FieldKeyHelper.Program.D3, FieldKeyHelper.OriginatingClass.Party, 1, 0)
@ -271,7 +271,7 @@ public class GameAccount : PersistentRPCObject
_currentToonId = value.PersistentID;
lock (DBGameAccount)
{
SetField(x=>x.LastPlayedHero = value.DBToon);
SetField(x=>x.LastPlayedHero = value.DbToon);
}
ChangedFields.SetPresenceFieldValue(LastPlayedHeroIdField);
@ -924,9 +924,9 @@ public class GameAccount : PersistentRPCObject
foreach (var hero in Toons)
profile.AddHeroes(HeroMiniProfile.CreateBuilder()
.SetHeroName(hero.Name)
.SetHeroGbidClass((int)hero.ClassID)
.SetHeroGbidClass((int)hero.ClassId)
.SetHeroFlags((uint)hero.Flags)
.SetHeroId((uint)hero.D3EntityID.IdLow)
.SetHeroId((uint)hero.D3EntityId.IdLow)
.SetHeroLevel(hero.Level)
.SetHeroVisualEquipment(hero.HeroVisualEquipmentField.Value)
);
@ -1423,7 +1423,7 @@ public class GameAccount : PersistentRPCObject
}
else if (queryKey.Group == 3 && queryKey.Field == 1) // Hero's class (GbidClass)
{
field.SetValue(bgs.protocol.Variant.CreateBuilder().SetIntValue(CurrentToon.ClassID).Build());
field.SetValue(bgs.protocol.Variant.CreateBuilder().SetIntValue(CurrentToon.ClassId).Build());
}
else if (queryKey.Group == 3 && queryKey.Field == 2) // Hero's current level
{

View File

@ -250,6 +250,8 @@ namespace DiIiS_NA.LoginServer.Base
}
internal class WebSocketServerProtocolHandshakeHandler : ChannelHandlerAdapter
{
static readonly Logger _logger = LogManager.CreateLogger();
private readonly string websocketPath;
private readonly string subprotocols;
@ -290,12 +292,16 @@ namespace DiIiS_NA.LoginServer.Base
{
if (!object.Equals(req.Method, HttpMethod.Get))
{
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.Forbidden));
SendHttpResponse(ctx, req,
new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.Forbidden));
return;
}
//v1.rpc.battle.net
//
WebSocketServerHandshakerFactory webSocketServerHandshakerFactory = new WebSocketServerHandshakerFactory(GetWebSocketLocation(ctx.Channel.Pipeline, req, websocketPath), subprotocols, allowExtensions, maxFramePayloadSize, allowMaskMismatch);
WebSocketServerHandshakerFactory webSocketServerHandshakerFactory =
new WebSocketServerHandshakerFactory(GetWebSocketLocation(ctx.Channel.Pipeline, req, websocketPath),
subprotocols, allowExtensions, maxFramePayloadSize, allowMaskMismatch);
WebSocketServerHandshaker handshaker = webSocketServerHandshakerFactory.NewHandshaker(req);
if (handshaker == null)
{
@ -303,7 +309,7 @@ namespace DiIiS_NA.LoginServer.Base
return;
}
handshaker.HandshakeAsync(ctx.Channel, req).ContinueWith(delegate (Task t)
handshaker.HandshakeAsync(ctx.Channel, req).ContinueWith(delegate(Task t)
{
if (t.Status != TaskStatus.RanToCompletion)
{
@ -311,12 +317,17 @@ namespace DiIiS_NA.LoginServer.Base
}
else
{
ctx.FireUserEventTriggered(new HandshakeHandler.HandshakeComplete(req.Uri, req.Headers, handshaker.SelectedSubprotocol));
ctx.FireUserEventTriggered(new HandshakeHandler.HandshakeComplete(req.Uri, req.Headers,
handshaker.SelectedSubprotocol));
}
}, TaskContinuationOptions.ExecuteSynchronously);
HandshakeHandler.SetHandshaker(ctx.Channel, handshaker);
ctx.Channel.Pipeline.Replace(this, "WS403Responder", HandshakeHandler.ForbiddenHttpRequestResponder());
}
catch (Exception ex)
{
_logger.ErrorException(ex, "Handshake failure");
}
finally
{
req.Release();
@ -344,19 +355,12 @@ namespace DiIiS_NA.LoginServer.Base
private static string GetWebSocketLocation(IChannelPipeline cp, IHttpRequest req, string path)
{
string str = "ws";
if (cp.Get<TlsHandler>() != null)
{
str = "wss";
}
string protocol = cp.Get<TlsHandler>() != null ? "wss" : "ws";
string str2 = null;
if (req.Headers.TryGet(HttpHeaderNames.Host, out ICharSequence value))
{
str2 = value.ToString();
}
// Ignore the Host header and default to a placeholder or IP address
string host = "192.168.1.100"; // Replace with your desired default host, e.g., the server's IP or DNS.
return str + "://" + str2 + path;
return $"{protocol}://{host}{path}";
}
}
}

View File

@ -73,9 +73,11 @@ namespace DiIiS_NA.LoginServer.Battle
switch (message[0])
{
case "rngsr":
Logger.Info("test");
if (GameServers.ContainsKey(ipPort)) GameServers.Remove(ipPort);
string rgsIp = args[0];
int rgsPort = int.Parse(args[1].Trim());
Logger.Info("Range IP {0}:{1}", rgsIp, rgsPort);
GameServers.Add(ipPort, new ServerDescriptor { GameIp = rgsIp, GamePort = rgsPort });
Logger.Info("Game server was registered for Blizzless {0}:{1}.", rgsIp, rgsPort);
break;

View File

@ -16,6 +16,8 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Security;
using System.Threading.Tasks;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Text;
@ -501,10 +503,41 @@ namespace DiIiS_NA.LoginServer.Battle
}
public void SendMotd()
{
if (string.IsNullOrWhiteSpace(LoginServerConfig.Instance.Motd) || !LoginServerConfig.Instance.MotdEnabled)
return;
Logger.Debug($"Motd sent to {Account.BattleTag}.");
SendServerWhisper(LoginServerConfig.Instance.Motd);
if (LoginServerConfig.Instance.MotdEnabled)
{
if (LoginServerConfig.Instance.MotdEnabledRemote)
{
if (string.IsNullOrWhiteSpace(LoginServerConfig.Instance.MotdRemoteUrl))
{
Logger.Warn("No Motd remote URL defined, falling back to normal motd.");
}
else
{
var url = LoginServerConfig.Instance.MotdRemoteUrl.Trim();
HttpClient client = new();
var post = client.PostAsJsonAsync(url, new
{
GameAccountId = InGameClient.Player?.Toon?.GameAccountId ?? 0,
ToonName = InGameClient.Player?.Toon?.Name ?? string.Empty,
WorldGlobalId = InGameClient.Player?.World?.GlobalID ?? 0
}).Result;
if (post.IsSuccessStatusCode)
{
var text = post.Content.ReadAsStringAsync().Result;
SendServerWhisper(text);
Logger.Info("Remote Motd sent successfully.");
return;
}
Logger.Warn("Could not POST to $[red]$" + url + "$[/]$. Please ensure the URL is correct. Falling back to normal MotD if available.");
}
}
if (!string.IsNullOrWhiteSpace(LoginServerConfig.Instance.Motd))
{
Logger.Debug($"Motd sent to {Account.BattleTag}.");
SendServerWhisper(LoginServerConfig.Instance.Motd);
}
}
}
public override void ChannelInactive(IChannelHandlerContext context)

View File

@ -32,10 +32,8 @@
{
index2 = (byte)(key[index1] + _state[counter] + index2);
// swap byte
byte tmp = _state[counter];
_state[counter] = _state[index2];
_state[index2] = tmp;
index1 = (byte)((index1 + 1) % key.Length);
(_state[counter], _state[index2]) = (_state[index2], _state[counter]);
index1 = (byte)((index1 + 1) % key.Length);
}
}
@ -46,11 +44,9 @@
x = (byte)(x + 1);
y = (byte)(_state[x] + y);
// swap byte
byte tmp = _state[x];
_state[x] = _state[y];
_state[y] = tmp;
(_state[x], _state[y]) = (_state[y], _state[x]);
byte xorIndex = (byte)(_state[x] + _state[y]);
byte xorIndex = (byte)(_state[x] + _state[y]);
outputBuffer[outputOffset + counter] = (byte)(inputBuffer[inputOffset + counter] ^ _state[xorIndex]);
}
return inputCount;

View File

@ -46,9 +46,9 @@ namespace DiIiS_NA.LoginServer.GamesSystem
{
this.GameCreateParams = D3.OnlineService.GameCreateParams.ParseFrom(attribute.Value.BlobValue);
if (this.GameCreateParams.CreationFlags == 256 || this.GameCreateParams.CreationFlags == 262400) this.Public = true;
lock (owner.Account.GameAccount.CurrentToon.DBToon)
lock (owner.Account.GameAccount.CurrentToon.DbToon)
{
var toonByClient = owner.Account.GameAccount.CurrentToon.DBToon;
var toonByClient = owner.Account.GameAccount.CurrentToon.DbToon;
toonByClient.CurrentAct = this.GameCreateParams.CampaignOrAdventureMode.Act;
toonByClient.CurrentQuestId = (this.GameCreateParams.CampaignOrAdventureMode.SnoQuest == 0 ? 87700 : this.GameCreateParams.CampaignOrAdventureMode.SnoQuest);
toonByClient.CurrentQuestStepId = (this.GameCreateParams.CampaignOrAdventureMode.QuestStepId == 0 ? -1 : this.GameCreateParams.CampaignOrAdventureMode.QuestStepId);
@ -75,7 +75,7 @@ namespace DiIiS_NA.LoginServer.GamesSystem
public void StartGame(List<BattleClient> clients, ulong objectId)
{
Logger.MethodTrace($"objectId: {objectId}");
var owner = this.Owner.Account.GameAccount.CurrentToon.DBToon;
var owner = this.Owner.Account.GameAccount.CurrentToon.DbToon;
if (Program.BattleBackend.GameServers.Count == 0) return;

View File

@ -48,16 +48,34 @@ namespace DiIiS_NA.LoginServer
set => Set(nameof(MotdEnabled), value);
}
public bool MotdEnabledWhenWorldLoads
{
get => GetBoolean(nameof(MotdEnabledWhenWorldLoads), false);
set => Set(nameof(MotdEnabledWhenWorldLoads), value);
}
/// <summary>
/// Motd text
/// </summary>
public string Motd
{
get => GetString(nameof(Motd),
$"Welcome to Blizzless Server Build {Program.Build} - Stage: {Program.Stage} [{Program.TypeBuild}]!");
$"Welcome to Blizzless Server Build {Program.BUILD} - Stage: {Program.STAGE} [{Program.TypeBuild}]!");
set => Set(nameof(Motd), value);
}
public bool MotdEnabledRemote
{
get => GetBoolean(nameof(MotdEnabledRemote), false);
set => Set(nameof(MotdEnabledRemote), value);
}
public string MotdRemoteUrl
{
get => GetString(nameof(MotdRemoteUrl), "");
set => Set(nameof(MotdRemoteUrl), value);
}
public static readonly LoginServerConfig Instance = new();
private LoginServerConfig() : base("Battle-Server")

View File

@ -97,9 +97,9 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
var paramsBuilder = D3.OnlineService.GameCreateParams.CreateBuilder(gameCreateParams);
var Mode = D3.OnlineService.CampaignOrAdventureModeCreateParams.CreateBuilder(gameCreateParams.CampaignOrAdventureMode);
lock (((HandlerController) controller).Client.Account.GameAccount.CurrentToon.DBToon)
lock (((HandlerController) controller).Client.Account.GameAccount.CurrentToon.DbToon)
{
DBToon toonByClient = (((HandlerController) controller).Client).Account.GameAccount.CurrentToon.DBToon;
DBToon toonByClient = (((HandlerController) controller).Client).Account.GameAccount.CurrentToon.DbToon;
if(toonByClient.CurrentAct == 400)
toonByClient.CurrentAct = gameCreateParams.CampaignOrAdventureMode.Act;
if (!clear_quests)
@ -152,7 +152,7 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
{
var gameCreateParamsBuilder = D3.OnlineService.GameCreateParams.CreateBuilder();
var toon = (((HandlerController) controller).Client).Account.GameAccount.CurrentToon;
var dbToon = (((HandlerController) controller).Client).Account.GameAccount.CurrentToon.DBToon;
var dbToon = (((HandlerController) controller).Client).Account.GameAccount.CurrentToon.DbToon;
gameCreateParamsBuilder.SetGameType(1);
gameCreateParamsBuilder.SetCreationFlags(0);
gameCreateParamsBuilder.SetCampaignOrAdventureMode(D3.OnlineService.CampaignOrAdventureModeCreateParams.CreateBuilder()
@ -160,7 +160,7 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
.SetAct(dbToon.CurrentAct)
.SetSnoQuest(dbToon.CurrentQuestId)
.SetQuestStepId(dbToon.CurrentQuestStepId)
.SetResumeFromSaveHeroId(toon.D3EntityID)
.SetResumeFromSaveHeroId(toon.D3EntityId)
.SetDeprecatedOpenToFriends(true)
.SetDeprecatedOpenToFriendsMessage("TestGame")
);

View File

@ -64,7 +64,7 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
foreach (Toon t in Client.Account.GameAccount.Toons)
{
d.AddDigestList(t.Digest);
GAS.AddHeroListOrder(t.D3EntityID);
GAS.AddHeroListOrder(t.D3EntityId);
}
Init.SetGameAccountSettings(GAS);
@ -77,7 +77,7 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
const int anniversaryEventStatus = 1;
const int challengeRiftNumber = 1;
const bool freeToPlay = true;
const bool storeStatus = false; // false
const bool storeStatus = true; // false (true - disabled, false - enabled)
const string catalogDigest = "C42DC6117A7008EDA2006542D6C07EAD096DAD90";
const string catalogVersion = "633565800390338000";
const int regionId = 1;
@ -92,8 +92,8 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
$" OnlineService.Store.Status={(storeStatus ? "1" : "0")}" + //Store Status, 0 - Enabled, 1 - Disabled
$" OnlineService.Store.ProductCatalogDigest={catalogDigest}" + //China
$" OnlineService.Store.ProductCatalogVersion={catalogVersion}" + //Chinese Catalog
//" OnlineService.Store.ProductCatalogDigest=79162283AFACCBA5DA989BD341F7B782860AC1F4" + //Euro
//" OnlineService.Store.ProductCatalogVersion=635984100169931000" + //Euro
//" OnlineService.Store.ProductCatalogDigest=79162283AFACCBA5DA989BD341F7B782860AC1F4" + //Euro
//" OnlineService.Store.ProductCatalogVersion=635984100169931000" + //Euro
$" OnlineService.Region.Id={regionId}"); //Region
Init.SetSeenTutorials(ByteString.CopyFrom(Client.Account.GameAccount.DBGameAccount.SeenTutorials));
@ -661,7 +661,7 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
if (hero != null)
{
var Snapshot = D3.Leaderboard.HeroSnapshot.CreateBuilder()
.SetHeroId(hero.D3EntityID)
.SetHeroId(hero.D3EntityId)
.AddCosmeticItems(D3.Leaderboard.HeroCosmeticItem.CreateBuilder().SetCosmeticVisualInventorySlot(1)
.SetGbid(hero.Cosmetic1))
.AddCosmeticItems(D3.Leaderboard.HeroCosmeticItem.CreateBuilder().SetCosmeticVisualInventorySlot(2)
@ -671,21 +671,21 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
.AddCosmeticItems(D3.Leaderboard.HeroCosmeticItem.CreateBuilder().SetCosmeticVisualInventorySlot(4)
.SetGbid(hero.Cosmetic4))
.SetActiveSkills(SkillsWithRunes.CreateBuilder()
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DBActiveSkills.Skill0)
.SetRuneType(hero.DBActiveSkills.Rune0))
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DBActiveSkills.Skill1)
.SetRuneType(hero.DBActiveSkills.Rune1))
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DBActiveSkills.Skill2)
.SetRuneType(hero.DBActiveSkills.Rune2))
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DBActiveSkills.Skill3)
.SetRuneType(hero.DBActiveSkills.Rune3))
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DBActiveSkills.Skill4)
.SetRuneType(hero.DBActiveSkills.Rune4))
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DBActiveSkills.Skill5)
.SetRuneType(hero.DBActiveSkills.Rune5)))
.SetActiveTraits(PassiveSkills.CreateBuilder().AddSnoTraits(hero.DBActiveSkills.Passive0)
.AddSnoTraits(hero.DBActiveSkills.Passive1).AddSnoTraits(hero.DBActiveSkills.Passive2)
.AddSnoTraits(hero.DBActiveSkills.Passive3));
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DbActiveSkills.Skill0)
.SetRuneType(hero.DbActiveSkills.Rune0))
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DbActiveSkills.Skill1)
.SetRuneType(hero.DbActiveSkills.Rune1))
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DbActiveSkills.Skill2)
.SetRuneType(hero.DbActiveSkills.Rune2))
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DbActiveSkills.Skill3)
.SetRuneType(hero.DbActiveSkills.Rune3))
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DbActiveSkills.Skill4)
.SetRuneType(hero.DbActiveSkills.Rune4))
.AddRunes(SkillWithRune.CreateBuilder().SetSkill(hero.DbActiveSkills.Skill5)
.SetRuneType(hero.DbActiveSkills.Rune5)))
.SetActiveTraits(PassiveSkills.CreateBuilder().AddSnoTraits(hero.DbActiveSkills.Passive0)
.AddSnoTraits(hero.DbActiveSkills.Passive1).AddSnoTraits(hero.DbActiveSkills.Passive2)
.AddSnoTraits(hero.DbActiveSkills.Passive3));
foreach (var item in hero.Profile.Equipment.ItemsList)
{
@ -842,7 +842,7 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
.SetHeroAltLevel((uint)gameAccount2.DBGameAccount.ParagonLevel)
.SetHeroFlags((uint)Hero.Flags)
.SetHeroLevel((uint)Hero.Level)
.SetHeroGbidClass((uint)Hero.ClassID)
.SetHeroGbidClass((uint)Hero.ClassId)
.SetHeroName(Hero.Name)
.SetHeroSnapshotAvailable(true)
.SetHeroVisualEquipment(gameAccount2.Toons[0].Digest.VisualEquipment);
@ -1001,7 +1001,7 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
.SetHeroAltLevel((uint)gameAccount.DBGameAccount.ParagonLevel)
.SetHeroFlags((uint)hero.Flags)
.SetHeroLevel((uint)hero.Level)
.SetHeroGbidClass((uint)hero.ClassID)
.SetHeroGbidClass((uint)hero.ClassId)
.SetHeroName(hero.Name)
.SetHeroSnapshotAvailable(true)
.SetHeroVisualEquipment(gameAccount.Toons[0].Digest.VisualEquipment);
@ -1097,7 +1097,7 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
var newToon = ToonManager.CreateNewToon(createParams.Name, createParams.GbidClass,
createParams.IsFemale ? ToonFlags.Female : ToonFlags.Male, 1, createParams.IsHardcore,
client.Account.GameAccount, createParams.IsSeason ? 1 : 0);
return CreateHeroResponse.CreateBuilder().SetHeroId(newToon.D3EntityID.IdLow).Build().ToByteString();
return CreateHeroResponse.CreateBuilder().SetHeroId(newToon.D3EntityId.IdLow).Build().ToByteString();
}
private ByteString OnHeroDeleteParams(BattleClient client, ByteString data)
@ -3118,7 +3118,7 @@ namespace DiIiS_NA.LoginServer.ServicesSystem.Services
var Response = RebirthHeroResponse.CreateBuilder();
foreach (Toon t in client.Account.GameAccount.Toons)
{
if (t.D3EntityID.IdLow == Request.HeroId)
if (t.D3EntityId.IdLow == Request.HeroId)
{
//t.SetSeason(1);

View File

@ -19,12 +19,12 @@ namespace DiIiS_NA.LoginServer.Toons
public int Cosmetic1
{
get => DBToon.Cosmetic1;
get => DbToon.Cosmetic1;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Cosmetic1 = value;
DBSessions.SessionUpdate(dbToon);
}
@ -33,12 +33,12 @@ namespace DiIiS_NA.LoginServer.Toons
public int Cosmetic2
{
get => DBToon.Cosmetic2;
get => DbToon.Cosmetic2;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Cosmetic2 = value;
DBSessions.SessionUpdate(dbToon);
}
@ -47,12 +47,12 @@ namespace DiIiS_NA.LoginServer.Toons
public int Cosmetic3
{
get => DBToon.Cosmetic3;
get => DbToon.Cosmetic3;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Cosmetic3 = value;
DBSessions.SessionUpdate(dbToon);
}
@ -61,12 +61,12 @@ namespace DiIiS_NA.LoginServer.Toons
public int Cosmetic4
{
get => DBToon.Cosmetic4;
get => DbToon.Cosmetic4;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Cosmetic4 = value;
DBSessions.SessionUpdate(dbToon);
}
@ -75,7 +75,7 @@ namespace DiIiS_NA.LoginServer.Toons
#endregion
public DBToon DBToon
public DBToon DbToon
{
get;
/*
@ -93,11 +93,11 @@ namespace DiIiS_NA.LoginServer.Toons
set;
}
private DBToon CachedDBToon { get; set; }
private DBToon CachedDbToon { get; set; }
public bool IsHardcore { get; set; }
public DBActiveSkills DBActiveSkills
public DBActiveSkills DbActiveSkills
{
get { return DBSessions.SessionQuerySingle<DBActiveSkills>(s => s.DBToon.Id == PersistentID); }
set { }
@ -108,7 +108,7 @@ namespace DiIiS_NA.LoginServer.Toons
get
{
var val = new IntPresenceField(FieldKeyHelper.Program.D3, FieldKeyHelper.OriginatingClass.Hero, 1, 0,
ClassID);
ClassId);
return val;
}
}
@ -152,13 +152,13 @@ namespace DiIiS_NA.LoginServer.Toons
FieldKeyHelper.OriginatingClass.Hero, 5, 0, _heroName);
private D3.Hero.VisualEquipment _visualEquipment = null;
public bool _visualEquipmentChanged = true;
public bool VisualEquipmentChanged = true;
public ByteStringPresenceField<D3.Hero.VisualEquipment> HeroVisualEquipmentField
{
get
{
if (_visualEquipmentChanged)
if (VisualEquipmentChanged)
{
var visualItems = new[]
{
@ -171,7 +171,7 @@ namespace DiIiS_NA.LoginServer.Toons
D3.Hero.VisualItem.CreateBuilder().SetEffectLevel(0).Build(), // Shoulders
D3.Hero.VisualItem.CreateBuilder().SetEffectLevel(0).Build(), // Legs
};
var CosmeticItems = new[]
var cosmeticItems = new[]
{
D3.Hero.VisualCosmeticItem.CreateBuilder().SetGbid(Cosmetic1).Build(), // Wings
D3.Hero.VisualCosmeticItem.CreateBuilder().SetGbid(Cosmetic2).Build(), // Flag
@ -197,8 +197,8 @@ namespace DiIiS_NA.LoginServer.Toons
}
_visualEquipment = D3.Hero.VisualEquipment.CreateBuilder().AddRangeVisualItem(visualItems)
.AddRangeCosmeticItem(CosmeticItems).Build();
_visualEquipmentChanged = false;
.AddRangeCosmeticItem(cosmeticItems).Build();
VisualEquipmentChanged = false;
}
return new ByteStringPresenceField<D3.Hero.VisualEquipment>(FieldKeyHelper.Program.D3,
@ -212,19 +212,19 @@ namespace DiIiS_NA.LoginServer.Toons
/// <summary>
/// D3 EntityID encoded id.
/// </summary>
public D3.OnlineService.EntityId D3EntityID { get; private set; }
public D3.OnlineService.EntityId D3EntityId { get; private set; }
/// <summary>
/// True if toon has been recently deleted;
/// </summary>
public bool Deleted
{
get => DBToon.Deleted;
get => DbToon.Deleted;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Deleted = value;
DBSessions.SessionUpdate(dbToon);
}
@ -235,12 +235,12 @@ namespace DiIiS_NA.LoginServer.Toons
public int SeasonCreated
{
get => DBToon.CreatedSeason;
get => DbToon.CreatedSeason;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.CreatedSeason = value;
DBSessions.SessionUpdate(dbToon);
}
@ -249,12 +249,12 @@ namespace DiIiS_NA.LoginServer.Toons
public bool StoneOfPortal
{
get => DBToon.StoneOfPortal;
get => DbToon.StoneOfPortal;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.StoneOfPortal = value;
DBSessions.SessionUpdate(dbToon);
}
@ -263,12 +263,12 @@ namespace DiIiS_NA.LoginServer.Toons
public bool Dead
{
get => DBToon.Dead;
get => DbToon.Dead;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Dead = value;
DBSessions.SessionUpdate(dbToon);
}
@ -280,12 +280,12 @@ namespace DiIiS_NA.LoginServer.Toons
/// </summary>
public bool Archieved
{
get => DBToon.Archieved;
get => DbToon.Archieved;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Archieved = value;
DBSessions.SessionUpdate(dbToon);
}
@ -319,9 +319,9 @@ namespace DiIiS_NA.LoginServer.Toons
set
{
_heroName = value;
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Name = value;
DBSessions.SessionUpdate(dbToon);
}
@ -350,9 +350,9 @@ namespace DiIiS_NA.LoginServer.Toons
set
{
GameAccountId = value.PersistentID;
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.DBGameAccount = value.DBGameAccount;
DBSessions.SessionUpdate(dbToon);
}
@ -414,9 +414,9 @@ namespace DiIiS_NA.LoginServer.Toons
set
{
_flags = value;
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Flags = value;
DBSessions.SessionUpdate(dbToon);
}
@ -440,7 +440,7 @@ namespace DiIiS_NA.LoginServer.Toons
{
if (_levelChanged || !LoginServerConfig.Instance.Enabled)
{
_cachedLevel = DBToon.Level;
_cachedLevel = DbToon.Level;
_levelChanged = false;
}
@ -448,10 +448,10 @@ namespace DiIiS_NA.LoginServer.Toons
}
private set
{
lock (DBToon)
lock (DbToon)
{
_cachedLevel = value;
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Level = value;
DBSessions.SessionUpdate(dbToon);
}
@ -459,7 +459,7 @@ namespace DiIiS_NA.LoginServer.Toons
}
private int _cachedParagonLevel = 0;
public bool _paragonLevelChanged = true;
public bool ParagonLevelChanged = true;
/// <summary>
/// Toon's Paragon level.
@ -468,9 +468,9 @@ namespace DiIiS_NA.LoginServer.Toons
{
get
{
if (!_paragonLevelChanged && LoginServerConfig.Instance.Enabled) return _cachedParagonLevel;
if (!ParagonLevelChanged && LoginServerConfig.Instance.Enabled) return _cachedParagonLevel;
_cachedParagonLevel = GameAccount.DBGameAccount.ParagonLevel;
_paragonLevelChanged = false;
ParagonLevelChanged = false;
return _cachedParagonLevel;
}
@ -494,16 +494,16 @@ namespace DiIiS_NA.LoginServer.Toons
/// </summary>
public long ExperienceNext
{
get => (Level >= 70 ? ParagonExperienceNext : DBToon.Experience);
get => (Level >= 70 ? ParagonExperienceNext : DbToon.Experience);
set
{
if (Level >= 70)
ParagonExperienceNext = value;
else
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.Experience = value;
DBSessions.SessionUpdate(dbToon);
}
@ -530,12 +530,12 @@ namespace DiIiS_NA.LoginServer.Toons
public int CurrentAct
{
get => DBToon.CurrentAct;
get => DbToon.CurrentAct;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.CurrentAct = value;
DBSessions.SessionUpdate(dbToon);
}
@ -544,12 +544,12 @@ namespace DiIiS_NA.LoginServer.Toons
public int CurrentQuestId
{
get => DBToon.CurrentQuestId;
get => DbToon.CurrentQuestId;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.CurrentQuestId = value;
DBSessions.SessionUpdate(dbToon);
}
@ -558,12 +558,12 @@ namespace DiIiS_NA.LoginServer.Toons
public int PvERating
{
get => DBToon.PvERating;
get => DbToon.PvERating;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.PvERating = value;
DBSessions.SessionUpdate(dbToon);
}
@ -572,12 +572,12 @@ namespace DiIiS_NA.LoginServer.Toons
public int CurrentQuestStepId
{
get => DBToon.CurrentQuestStepId;
get => DbToon.CurrentQuestStepId;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.CurrentQuestStepId = value;
DBSessions.SessionUpdate(dbToon);
}
@ -586,12 +586,12 @@ namespace DiIiS_NA.LoginServer.Toons
public int CurrentDifficulty
{
get => DBToon.CurrentDifficulty;
get => DbToon.CurrentDifficulty;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.CurrentDifficulty = value;
DBSessions.SessionUpdate(dbToon);
}
@ -606,11 +606,11 @@ namespace DiIiS_NA.LoginServer.Toons
get => GameAccount.DBGameAccount.TotalKilled;
set
{
var dbGA = GameAccount.DBGameAccount;
lock (dbGA)
var dbGa = GameAccount.DBGameAccount;
lock (dbGa)
{
dbGA.TotalKilled = value;
DBSessions.SessionUpdate(dbGA);
dbGa.TotalKilled = value;
DBSessions.SessionUpdate(dbGa);
}
}
}
@ -623,11 +623,11 @@ namespace DiIiS_NA.LoginServer.Toons
get => GameAccount.DBGameAccount.ElitesKilled;
set
{
var dbGA = GameAccount.DBGameAccount;
lock (dbGA)
var dbGa = GameAccount.DBGameAccount;
lock (dbGa)
{
dbGA.ElitesKilled = value;
DBSessions.SessionUpdate(dbGA);
dbGa.ElitesKilled = value;
DBSessions.SessionUpdate(dbGa);
}
}
}
@ -650,19 +650,19 @@ namespace DiIiS_NA.LoginServer.Toons
}
set
{
var dbGA = GameAccount.DBGameAccount;
lock (dbGA)
var dbGa = GameAccount.DBGameAccount;
lock (dbGa)
{
if (IsHardcore)
{
dbGA.TotalBountiesHardcore = value;
dbGa.TotalBountiesHardcore = value;
}
else
{
dbGA.TotalBounties = value;
dbGa.TotalBounties = value;
}
DBSessions.SessionUpdate(dbGA);
DBSessions.SessionUpdate(dbGa);
}
}
}
@ -672,10 +672,10 @@ namespace DiIiS_NA.LoginServer.Toons
/// </summary>
public int SeasonalKills
{
get => DBToon.Kills;
get => DbToon.Kills;
set
{
var dbToon = DBToon;
var dbToon = DbToon;
lock (dbToon)
{
dbToon.Kills = value;
@ -714,12 +714,12 @@ namespace DiIiS_NA.LoginServer.Toons
/// </summary>
public int CollectedGoldSeasonal
{
get => DBToon.GoldGained;
get => DbToon.GoldGained;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.GoldGained = value;
DBSessions.SessionUpdate(dbToon);
}
@ -731,12 +731,12 @@ namespace DiIiS_NA.LoginServer.Toons
/// </summary>
public int TimePlayed
{
get => DBToon.TimePlayed;
get => DbToon.TimePlayed;
set
{
lock (DBToon)
lock (DbToon)
{
var dbToon = DBToon;
var dbToon = DbToon;
dbToon.TimePlayed = value;
DBSessions.SessionUpdate(dbToon);
}
@ -751,7 +751,7 @@ namespace DiIiS_NA.LoginServer.Toons
/// <summary>
/// Database handler for this toon
/// </summary>
public GameDBSession DBSession { get; set; }
public GameDBSession DbSession { get; set; }
/// <summary>
/// Settings for toon.
@ -772,14 +772,14 @@ namespace DiIiS_NA.LoginServer.Toons
{
get
{
var dbToon = DBToon;
var dbToon = DbToon;
if (IsHardcore) dbToon.Flags |= ToonFlags.Hardcore;
//var isSeason = Convert.ToUInt16(isSeassoned);
var digest = D3.Hero.Digest.CreateBuilder().SetVersion(905)
.SetHeroId(D3EntityID)
.SetHeroId(D3EntityId)
.SetHeroName(Name)
.SetGbidClass((int)ClassID)
.SetGbidClass((int)ClassId)
.SetLevel(Level)
//deprecated //.SetAltLevel(dbToon.ParagonLevel)
.SetPlayerFlags((uint)dbToon.Flags) // + isSeason)
@ -936,7 +936,7 @@ namespace DiIiS_NA.LoginServer.Toons
.SetIdHigh(0x3C000002517A293 + invItem.Id))
.SetHirelingClass(0)
.SetItemSlot(272 + invItem.EquipmentSlot * 16)
.SetOwnerEntityId(D3EntityID)
.SetOwnerEntityId(D3EntityId)
.SetSquareIndex(0)
.SetUsedSocketCount(0);
@ -1014,7 +1014,7 @@ namespace DiIiS_NA.LoginServer.Toons
}
//*/
var dbToon = DBToon;
var dbToon = DbToon;
string[] stats = dbToon.Stats.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var profile = D3.Profile.HeroProfile.CreateBuilder()
.SetHardcore(IsHardcore)
@ -1022,7 +1022,7 @@ namespace DiIiS_NA.LoginServer.Toons
//deprecated //.SetLife(0)
.SetSnoKillLocation(71150)
.SetKillerInfo(D3.Profile.KillerInfo.CreateBuilder().SetSnoKiller(6031).SetRarity(4))
.SetHeroId(D3EntityID)
.SetHeroId(D3EntityId)
//deprecated //.SetHighestDifficulty(0)
.SetHighestLevel(dbToon.Level)
//.SetMonstersKilled(111)
@ -1045,9 +1045,9 @@ namespace DiIiS_NA.LoginServer.Toons
//.SetResistCold(110)
//.SetResistPoison(111)
.SetEquipment(itemList);
if (DBActiveSkills != null)
if (DbActiveSkills != null)
{
var dbActiveSkills = DBActiveSkills;
var dbActiveSkills = DbActiveSkills;
var skills = new[]
{
D3.Profile.SkillWithRune.CreateBuilder()
@ -1104,7 +1104,7 @@ namespace DiIiS_NA.LoginServer.Toons
}
}
public int ClassID =>
public int ClassId =>
Class switch
{
ToonClass.Barbarian => 0x4FB91EE2,
@ -1118,7 +1118,7 @@ namespace DiIiS_NA.LoginServer.Toons
};
// Used for Conversations
public int VoiceClassID =>
public int VoiceClassId =>
Class switch
{
ToonClass.DemonHunter => 0,
@ -1139,21 +1139,21 @@ namespace DiIiS_NA.LoginServer.Toons
private readonly Dictionary<int, int> _visualToSlotMapping = new() { { 1, 0 }, { 2, 1 }, { 7, 2 }, { 5, 3 }, { 4, 4 }, { 3, 5 }, { 8, 6 }, { 9, 7 } };
private static readonly Core.MPQ.FileFormats.GameBalance HeroData =
public static readonly Core.MPQ.FileFormats.GameBalance HeroData =
(Core.MPQ.FileFormats.GameBalance)MPQStorage.Data.Assets[SNOGroup.GameBalance][19740].Data;
public Toon(DBToon dbToon, GameDBSession DBSession = null)
public Toon(DBToon dbToon, GameDBSession dbSession = null)
: base(dbToon.Id)
{
D3EntityID = D3.OnlineService.EntityId.CreateBuilder().SetIdHigh((ulong)EntityIdHelper.HighIdType.ToonId)
D3EntityId = D3.OnlineService.EntityId.CreateBuilder().SetIdHigh((ulong)EntityIdHelper.HighIdType.ToonId)
.SetIdLow(PersistentID).Build();
_heroName = dbToon.Name;
_flags = dbToon.Flags;
GameAccountId = dbToon.DBGameAccount.Id;
_toonClass = dbToon.Class;
DBToon = dbToon;
this.DBSession = DBSession;
DbToon = dbToon;
this.DbSession = dbSession;
IsHardcore = dbToon.isHardcore;
IsSeasoned = dbToon.isSeasoned;
HeroTable = HeroData.Heros.Find(item => item.Name == Class.ToString());
@ -1190,7 +1190,7 @@ namespace DiIiS_NA.LoginServer.Toons
{
var questHistory = new DBQuestHistory
{
DBToon = DBToon,
DBToon = DbToon,
QuestId = quest,
QuestStep = -1,
isCompleted = true
@ -1209,8 +1209,8 @@ namespace DiIiS_NA.LoginServer.Toons
public void StateChanged()
{
_levelChanged = true;
_paragonLevelChanged = true;
_visualEquipmentChanged = true;
ParagonLevelChanged = true;
VisualEquipmentChanged = true;
}
#region Notifications
@ -1242,7 +1242,7 @@ namespace DiIiS_NA.LoginServer.Toons
#endregion
public static ToonClass GetClassByID(int classId) =>
public static ToonClass GetClassById(int classId) =>
classId switch
{
0x4FB91EE2 => ToonClass.Barbarian,
@ -1257,7 +1257,7 @@ namespace DiIiS_NA.LoginServer.Toons
public override string ToString()
{
return $"{{ Toon: {Name} [lowId: {D3EntityID.IdLow}] }}";
return $"{{ Toon: {Name} [lowId: {D3EntityId.IdLow}] }}";
}
}

View File

@ -240,7 +240,7 @@ namespace DiIiS_NA.LoginServer.Toons
var newDBToon = new DBToon
{
Class = @Toon.GetClassByID(classId),
Class = @Toon.GetClassById(classId),
Name = name,
/*HashCode = GetUnusedHashCodeForToonName(name),*/
Flags = toonFlags,
@ -309,19 +309,19 @@ namespace DiIiS_NA.LoginServer.Toons
public static void CreateStartEquipment(Toon toon, bool isHardcore)
{
DBInventory pants = NewbiePants;
pants.DBToon = toon.DBToon;
pants.DBToon = toon.DbToon;
pants.DBGameAccount = toon.GameAccount.DBGameAccount;
pants.isHardcore = isHardcore;
DBSessions.SessionSave(pants);
DBInventory armor = NewbieArmor;
armor.DBToon = toon.DBToon;
armor.DBToon = toon.DbToon;
armor.DBGameAccount = toon.GameAccount.DBGameAccount;
armor.isHardcore = isHardcore;
DBSessions.SessionSave(armor);
DBInventory weapon;
switch (toon.DBToon.Class)
switch (toon.DbToon.Class)
{
case ToonClass.Barbarian:
weapon = NewbieAxe;
@ -348,14 +348,14 @@ namespace DiIiS_NA.LoginServer.Toons
weapon = NewbieKnife;
break;
}
weapon.DBToon = toon.DBToon;
weapon.DBToon = toon.DbToon;
weapon.DBGameAccount = toon.GameAccount.DBGameAccount;
weapon.isHardcore = isHardcore;
DBSessions.SessionSave(weapon);
if (toon.DBToon.Class == ToonClass.Crusader) //add shield
if (toon.DbToon.Class == ToonClass.Crusader) //add shield
{
weapon = NewbieShield;
weapon.DBToon = toon.DBToon;
weapon.DBToon = toon.DbToon;
weapon.DBGameAccount = toon.GameAccount.DBGameAccount;
weapon.isHardcore = isHardcore;
DBSessions.SessionSave(weapon);
@ -367,7 +367,7 @@ namespace DiIiS_NA.LoginServer.Toons
DBSessions.SessionSave(new DBHireling
{
Class = type,
DBToon = toon.DBToon,
DBToon = toon.DbToon,
Skill1SNOId = -1,
Skill2SNOId = -1,
Skill3SNOId = -1,

View File

@ -11,6 +11,7 @@
<StartupObject>DiIiS_NA.Program</StartupObject>
<DebugType>full</DebugType>
<Configurations>Debug;Release;github</Configurations>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -118,6 +119,9 @@
<None Update="DiIiS_Battle.net.p12">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="config.mods.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,11 @@
using System;
namespace DiIiS_NA.Core.Extensions;
public static class MathConversionsOperations
{
public static int Floor(this float value) => (int) Math.Floor(value);
public static int Ceiling(this float value) => (int) Math.Ceiling(value);
public static int Floor(this double value) => (int) Math.Floor(value);
public static int Ceil(this double value) => (int) Math.Floor(value);
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
namespace DiIiS_NA.Core.Extensions
{
@ -6,22 +7,12 @@ namespace DiIiS_NA.Core.Extensions
{
public static bool ContainsAtLeastOne<T>(this List<T> list1, List<T> list2)
{
foreach (T m in list2)
{
if (list1.Contains(m))
return true;
}
return false;
return list2.Any(list1.Contains);
}
public static bool ContainsAtLeastOne<T>(this List<T> list, T[] array)
{
foreach (T m in array)
{
if (list.Contains(m))
return true;
}
return false;
return array.Any(list.Contains);
}
}
}

View File

@ -44,15 +44,15 @@ namespace DiIiS_NA.Core.Extensions
byteArray[indexBA++] = (byte)item;
}
System.IO.MemoryStream ms = new System.IO.MemoryStream(byteArray);
System.IO.Compression.GZipStream sr = new System.IO.Compression.GZipStream(ms,
System.IO.MemoryStream ms = new(byteArray);
System.IO.Compression.GZipStream sr = new(ms,
System.IO.Compression.CompressionMode.Decompress);
byteArray = new byte[byteArray.Length];
int rByte = sr.Read(byteArray, 0, byteArray.Length);
System.Text.StringBuilder sB = new System.Text.StringBuilder(rByte);
System.Text.StringBuilder sB = new(rByte);
for (int i = 0; i < rByte; i++)
{
sB.Append((char)byteArray[i]);

View File

@ -219,4 +219,12 @@ namespace DiIiS_NA.Core.Helpers.Math
/// <returns></returns>
public bool Chance(float successPercentage) => Next(100) < successPercentage;
}
public static class NumberExtensions
{
public static float ToFloat(this double value, int decimals = 4) => (float)System.Math.Round(value, decimals);
public static float ToFloat(this float value, int decimals = 4) => (float)System.Math.Round(value, decimals);
public static double ToDouble(this float value, int decimals = 4) => (double)System.Math.Round(value, decimals);
public static double ToDouble(this double value, int decimals = 4) => (double)System.Math.Round(value, decimals);
}
}

View File

@ -23,6 +23,21 @@ public static class RandomHelper
return Random.Next(minValue, maxValue);
}
public static float NextFloat()
{
return (float)Random.NextDouble();
}
public static float NextFloat(float min, float max)
{
return min + (float)Random.NextDouble() * (max - min);
}
public static float NextFloat(float max)
{
return (float)Random.NextDouble() * max;
}
public static void NextBytes(byte[] buffer)
{
Random.NextBytes(buffer);

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@ -24,12 +26,12 @@ public class AnsiTarget : LogTarget
_table = new Table().Expand().ShowFooters().ShowHeaders().Border(TableBorder.Rounded);
if (IncludeTimeStamps)
_table.AddColumn("Time");
_table.AddColumn("Time").Centered();
_table
.AddColumn("Level")
.AddColumn("Logger")
.AddColumn("Message")
.AddColumn("Error");
.AddColumn("Level").RightAligned()
.AddColumn("Message").Centered()
.AddColumn("Logger").LeftAligned()
.AddColumn("Error").RightAligned();
AnsiConsole.Live(_table).StartAsync(async ctx =>
{
@ -56,14 +58,17 @@ public class AnsiTarget : LogTarget
});
}
public static void StopIfRunning()
public static void StopIfRunning(bool clear = false)
{
CancellationTokenSource.Cancel();
while(!_shutdown)
Thread.Sleep(100);
Thread.Sleep(1000);
AnsiConsole.Clear();
AnsiConsole.Cursor.SetPosition(0,0);
if (clear)
{
AnsiConsole.Clear();
AnsiConsole.Cursor.SetPosition(0, 0);
}
}
/// <summary>
@ -98,6 +103,15 @@ public class AnsiTarget : LogTarget
}
private static Dictionary<string, string> _replacements = new ()
{
["["] = "[[",
["]"] = "]]",
["$[[/]]$"] = "[/]",
["$[["] = "[",
["]]$"] = "]"
};
/// <summary>
/// Performs a cleanup on the target.
/// All [ becomes [[, and ] becomes ]] (for ignoring ANSI codes)
@ -109,124 +123,85 @@ public class AnsiTarget : LogTarget
/// </summary>
/// <param name="x"></param>
/// <returns></returns>
string Cleanup(string x) => Beautify(x.Replace("[", "[[").Replace("]", "]]").Replace("$[[/]]$", "[/]").Replace("$[[", "[").Replace("]]$", "]"));
public static string Cleanup(string input)
{
if (string.IsNullOrEmpty(input)) return "";
return Beautify(_replacements.Aggregate(input, (current, replacement) => current.Replace(replacement.Key, replacement.Value)));
}
public override void LogMessage(Logger.Level level, string logger, string message)
{
if (CancellationTokenSource.IsCancellationRequested)
return;
if (CancellationTokenSource.IsCancellationRequested) return;
try
{
if (IncludeTimeStamps)
_table.AddRow(
new Markup(DateTime.Now.ToString(TimeStampFormat), GetStyleByLevel(level)),
new Markup(level.ToString(), GetStyleByLevel(level)).RightJustified(),
new Markup(logger, GetStyleByLevel(level)).LeftJustified(),
new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(),
new Markup("", new Style(foreground: Color.Green3_1)).Centered());
else
_table.AddRow(
new Markup(level.ToString()).RightJustified(),
new Markup(logger, GetStyleByLevel(level)).LeftJustified(),
new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(),
new Markup("", new Style(foreground: Color.Green3_1)).Centered());
AddRow(level, logger, message, "");
}
catch (Exception ex)
{
var regex = new Regex(@"\$\[.*?\]\$");
var matches = regex.Matches(message);
foreach (Match match in matches)
{
message = message.Replace(match.Value, "");
}
if (IncludeTimeStamps)
{
_table.AddRow(
new Markup(DateTime.Now.ToString(TimeStampFormat), GetStyleByLevel(level)),
new Markup(level.ToString(), GetStyleByLevel(level)).RightJustified(),
new Markup(logger, GetStyleByLevel(level)).LeftJustified(),
new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(),
new Markup(ex.Message, new Style(foreground: Color.Red3_1)).Centered());
}
else
{
_table.AddRow(
new Markup(level.ToString()).RightJustified(),
new Markup(logger, GetStyleByLevel(level)).LeftJustified(),
new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(),
new Markup(ex.Message, new Style(foreground: Color.Red3_1)).Centered());
}
AddRow(level, logger, Cleanup(StripMarkup(message)), ex.Message, true);
}
}
public override void LogException(Logger.Level level, string logger, string message, Exception exception)
{
if (CancellationTokenSource.IsCancellationRequested)
return;
if (CancellationTokenSource.IsCancellationRequested) return;
try
{
if (IncludeTimeStamps)
_table.AddRow(
new Markup(DateTime.Now.ToString(TimeStampFormat), GetStyleByLevel(level)),
new Markup(level.ToString(), GetStyleByLevel(level)).RightJustified(),
new Markup(logger, GetStyleByLevel(level)).LeftJustified(),
new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(),
new Markup(
$"[underline red3_1 on white]{exception.GetType().Name}[/]\n" + Cleanup(exception.Message),
new Style(foreground: Color.Red3_1)).Centered());
else
_table.AddRow(
new Markup(level.ToString()).RightJustified(),
new Markup(logger, GetStyleByLevel(level)).LeftJustified(),
new Markup(message, GetStyleByLevel(level)).LeftJustified(),
new Markup(
$"[underline red3_1 on white]{exception.GetType().Name}[/]\n" + Cleanup(exception.Message),
new Style(foreground: Color.Red3_1)).Centered());
AddRow(level, logger, message, $"[underline red3_1 on white]{exception.GetType().Name}[/]\n" + Cleanup(exception.Message), exFormat: true);
}
catch (Exception ex)
{
var regex = new Regex(@"\$\[.*?\]\$");
var matches = regex.Matches(message);
foreach (Match match in matches)
{
message = message.Replace(match.Value, "");
}
if (IncludeTimeStamps)
{
_table.AddRow(
new Markup(DateTime.Now.ToString(TimeStampFormat), GetStyleByLevel(level)),
new Markup(level.ToString(), GetStyleByLevel(level)).RightJustified(),
new Markup(logger, GetStyleByLevel(level)).LeftJustified(),
new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(),
new Markup(ex.Message, new Style(foreground: Color.Red3_1)).Centered());
}
else
{
_table.AddRow(
new Markup(level.ToString()).RightJustified(),
new Markup(logger, GetStyleByLevel(level)).LeftJustified(),
new Markup(Cleanup(message), GetStyleByLevel(level)).LeftJustified(),
new Markup(ex.Message, new Style(foreground: Color.Red3_1)).Centered());
}
AddRow(level, logger, Cleanup(StripMarkup(message)), ex.Message, true);
}
}
}
private void AddRow(Logger.Level level, string logger, string message, string exMessage, bool isError = false,
bool exFormat = false)
{
Style messageStyle = GetStyleByLevel(level);
Style exStyle = exFormat ? new Style(foreground: Color.Red3_1) : new Style(foreground: Color.Green3_1);
var colTimestamp = new Markup(DateTime.Now.ToString(TimeStampFormat), messageStyle).Centered();
var colLevel = new Markup(level.ToString(), messageStyle).RightJustified();
var colMessage = new Markup(Cleanup(message), messageStyle).Centered();
var colLogger = new Markup(logger, new Style(messageStyle.Foreground, messageStyle.Background, messageStyle.Decoration
#if DEBUG
//, link = ...
#endif
)).LeftJustified();
var colError = new Markup(isError ? exMessage : "", exStyle).RightJustified();
if (IncludeTimeStamps) _table.AddRow(colTimestamp, colLevel, colMessage, colLogger, colError);
else _table.AddRow(colLevel, colMessage, colLogger, colError);
}
private string StripMarkup(string message)
{
var regex = new Regex(@"\$\[.*?\]\$");
var matches = regex.Matches(message);
foreach (Match match in matches)
{
message = message.Replace(match.Value, "");
}
return message;
}
private static Style GetStyleByLevel(Logger.Level level)
{
return level switch
{
Logger.Level.RenameAccountLog => new Style(Color.DarkSlateGray3),//
Logger.Level.ChatMessage => new Style(Color.DarkSlateGray2),//
Logger.Level.Debug => new Style(Color.Olive),//
Logger.Level.MethodTrace => new Style(Color.DarkOliveGreen1_1),//
Logger.Level.Trace => new Style(Color.BlueViolet),//
Logger.Level.Info => new Style(Color.White),
Logger.Level.Success => new Style(Color.Green3_1),
Logger.Level.Warn => new Style(Color.Yellow),//
Logger.Level.RenameAccountLog => new Style(Color.Gold1),//
Logger.Level.ChatMessage => new Style(Color.Plum2),//
Logger.Level.Debug => new Style(Color.Grey62),//
Logger.Level.MethodTrace => new Style(Color.Grey74, decoration: Decoration.Dim | Decoration.Italic),//
Logger.Level.Trace => new Style(Color.Grey82),//
Logger.Level.Info => new Style(Color.SteelBlue),
Logger.Level.Success => new Style(Color.DarkOliveGreen3_2),
Logger.Level.Warn => new Style(Color.DarkOrange),//
Logger.Level.Error => new Style(Color.IndianRed1),//
Logger.Level.Fatal => new Style(Color.Red3_1),//
Logger.Level.QuestInfo => new Style(Color.Plum2),
Logger.Level.QuestStep => new Style(Color.Plum3, decoration: Decoration.Dim),
Logger.Level.PacketDump => new Style(Color.Maroon),//
_ => new Style(Color.White)
};
@ -239,4 +214,7 @@ public static class AnsiTargetExtensions
{
return text.Replace("$[", "").Replace("]$", "").Replace("[", "[[").Replace("]", "]]");
}
public static string StyleAnsi(this object obj, string style) =>
$"$[{style}]$" + obj.ToString().EscapeMarkup() + "$[/]$";
}

View File

@ -26,7 +26,7 @@ namespace DiIiS_NA.Core.Logging
public override void LogMessage(Logger.Level level, string logger, string message)
{
var timeStamp = IncludeTimeStamps ? "[[" + DateTime.Now.ToString(TimeStampFormat) + "]] " : "";
AnsiConsole.MarkupLine($"{timeStamp}{SetColor(level, true)}[[{level.ToString(),8}]][/] {SetColor(level)}[[{Cleanup(logger),20}]]: {Cleanup(message)}[/]");
AnsiConsole.MarkupLine($"{timeStamp}{SetColor(level, true)}[[{level.ToString(),8}]][/] {SetColor(level)}[[{Cleanup(logger),20}]]: {AnsiTarget.Cleanup(message)}[/]");
}
/// <param name="level">Log level.</param>

View File

@ -74,6 +74,12 @@
set => Set(nameof(TimeStampFormat), value);
}
public bool MaximizeWhenEnabled
{
get => GetBoolean(nameof(MaximizeWhenEnabled), false);
set => Set(nameof(MaximizeWhenEnabled), value);
}
public LogTargetConfig(string loggerName) : base(loggerName) { }
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace DiIiS_NA.Core.Logging
{
@ -21,34 +22,30 @@ namespace DiIiS_NA.Core.Logging
/// </summary>
internal readonly static Dictionary<string, Logger> Loggers = new();
/// <summary>
/// Creates and returns a logger named with declaring type.
/// </summary>
/// <returns>A <see cref="Logger"/> instance.</returns>
public static Logger CreateLogger()
{
var frame = new StackFrame(1, false); // read stack frame.
var name = frame.GetMethod().DeclaringType.Name; // get declaring type's name.
if (name == null) // see if we got a name.
throw new Exception("Error getting full name for declaring type.");
return CreateLogger(name); // return the newly created logger.
}
/// <summary>
/// Creates and returns a logger with given name.
/// </summary>
/// <param name="name"></param>
/// <returns>A <see cref="Logger"/> instance.</returns>
public static Logger CreateLogger(string name)
public static Logger CreateLogger(string? name = null, [CallerFilePath] string filePath = "")
{
if (name == null)
{
var frame = new StackFrame(1, false); // read stack frame.
name = frame.GetMethod()?.DeclaringType?.Name ?? "Unknown"; // get declaring type's name.
}
if (!Loggers.ContainsKey(name)) // see if we already have instance for the given name.
Loggers.Add(name, new Logger(name)); // add it to dictionary of loggers.
Loggers.Add(name, new Logger(name, filePath)); // add it to dictionary of loggers.
return Loggers[name]; // return the newly created logger.
}
public static Logger CreateLogger<T>([CallerFilePath] string filePath = "")
{
return CreateLogger(typeof(T).Name, filePath);
}
/// <summary>
/// Attachs a new log target.
/// </summary>

View File

@ -13,15 +13,17 @@ namespace DiIiS_NA.Core.Logging
public class Logger
{
public string Name { get; protected set; }
public string FilePath { get; protected set; }
/// <summary>
/// A logger base type is used to create a logger instance.
/// E.g. ConsoleTarget, FileTarget, etc.
/// </summary>
/// <param name="name">Logger name</param>
public Logger(string name)
public Logger(string name, string filePath = null)
{
Name = name;
FilePath = filePath;
}
public enum Level
@ -66,6 +68,15 @@ namespace DiIiS_NA.Core.Logging
/// Fatal messages (usually unrecoverable errors that leads to client or server crashes).
/// </summary>
Fatal,
/// <summary>
/// The messages meant for quest general logging purposes.
/// </summary>
QuestInfo,
/// <summary>
/// The messages meant for quest logging purposes.
/// </summary>
QuestStep,
/// <summary>
/// Packet messages.
/// </summary>
@ -105,14 +116,39 @@ namespace DiIiS_NA.Core.Logging
/// <param name="message">The log message.</param>
public void MethodTrace(string message, [CallerMemberName] string methodName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0)
{
var m = $"$[darkolivegreen3_2]${methodName}()$[/]$";
#if DEBUG
var fileName = Path.GetFileName(filePath);
Log(Level.MethodTrace, $"$[underline white]${fileName}:{lineNumber}$[/]$ $[darkolivegreen3_2]${methodName}()$[/]$: " + message, null);
Log(Level.MethodTrace, $"$[red]${fileName}:{lineNumber}$[/]$ in {m}: " + message, null);
#else
Log(Level.MethodTrace, $"$[darkolivegreen3_2]${methodName}()$[/]$: " + message, null);
Log(Level.MethodTrace, $"{m}: " + message, null);
#endif
}
public void QuestStep(string message, [CallerMemberName] string methodName = "",
[CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0)
{
var m = $"$[darkolivegreen3_2]${methodName}()$[/]$";
#if DEBUG
var fileName = Path.GetFileName(filePath);
Log(Level.MethodTrace, $"$[red]${fileName}:{lineNumber}$[/]$ in {m}: " + message, null);
#else
Log(Level.MethodTrace, $"{m}: " + message, null);
#endif
}
public void QuestInfo(string message, [CallerMemberName] string methodName = "",
[CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0)
{
var m = $"$[darkolivegreen3_2]${methodName}()$[/]$";
#if DEBUG
var fileName = Path.GetFileName(filePath);
Log(Level.MethodTrace, $"$[red]${fileName}:{lineNumber}$[/]$ in {m}: " + message, null);
#else
Log(Level.MethodTrace, $"{m}: " + message, null);
#endif
}
/// <param name="message">The log message.</param>
public void Debug(string message) => Log(Level.Debug, message, null);
@ -153,7 +189,7 @@ namespace DiIiS_NA.Core.Logging
/// <param name="message">The log message.</param>
/// <param name="args">Additional arguments.</param>
public void Fatal(string message, params object[] args) => Log(Level.Fatal, message, args);
public void Fatal(string message, params object[] args) => Log(Level.Fatal, message, args);
#endregion

View File

@ -79,7 +79,7 @@ namespace DiIiS_NA.Core.MPQ
public void Init()
{
Logger.Info("Loading Diablo III Assets..");
Logger.Info("Loading Diablo III Assets...");
DictSNOAccolade = Dicts.LoadAccolade();
DictSNOAct = Dicts.LoadActs();
DictSNOActor = Dicts.LoadActors();

View File

@ -30,11 +30,11 @@ namespace DiIiS_NA.Core.MPQ
var mpqFile = MPQStorage.GetMPQFile(file);
if (mpqFile == null)
{
Logger.Error("Cannot find base MPQ file: {0}.", file);
Logger.Fatal("Cannot find base MPQ file: $[white on red]${0}$[/]$.", file);
return;
}
this.BaseMPQFiles.Add(mpqFile);
Logger.Trace("Added MPQ storage: {0}.", file);
Logger.Debug($"Added MPQ storage: $[white underline]${file}$[/]$.");
}
this.PatchPattern = patchPattern;
@ -45,7 +45,7 @@ namespace DiIiS_NA.Core.MPQ
this.Loaded = true;
else
{
Logger.Error("Required patch-chain version {0} is not satified (found version: {1}).", this.RequiredVersion, topMostMPQVersion);
Logger.Error("Required patch-chain version {0} is not satisfied (found version: {1}).", this.RequiredVersion, topMostMPQVersion);
}
}

View File

@ -35,10 +35,13 @@ namespace DiIiS_NA.Core.Storage.AccountDataBase
if (_config == null)
{
_config = new Configuration();
_config = _config.Configure(Path.Combine(FileHelpers.AssemblyRoot, "database.Account.config"));
#if DEBUG
_config = _config.Configure(File.Exists(Path.Combine(FileHelpers.AssemblyRoot, "database.Account.Debug.config")) ? Path.Combine(FileHelpers.AssemblyRoot, "database.Account.Debug.config") : Path.Combine(FileHelpers.AssemblyRoot, "database.Account.config"));
#else
_config = _config.Configure(Path.Combine(FileHelpers.AssemblyRoot, "database.Account.config"));
#endif
var replacedProperties = new Dictionary<string, string>();
var replacedProperties = new Dictionary<string, string>();
foreach (var prop in _config.Properties)
{
var newvalue = prop.Value;

View File

@ -35,8 +35,11 @@ namespace DiIiS_NA.Core.Storage.WorldSceneBase
if (_config == null)
{
_config = new Configuration();
#if DEBUG
_config = _config.Configure(File.Exists(Path.Combine(FileHelpers.AssemblyRoot, "database.Worlds.Debug.config")) ? Path.Combine(FileHelpers.AssemblyRoot, "database.Worlds.Debug.config") : Path.Combine(FileHelpers.AssemblyRoot, "database.Worlds.config"));
#else
_config = _config.Configure(Path.Combine(FileHelpers.AssemblyRoot, "database.Worlds.config"));
#endif
var replacedProperties = new Dictionary<string, string>();
foreach (var prop in _config.Properties)

View File

@ -24,7 +24,7 @@ namespace DiIiS_NA.GameServer.ClientSystem.Base
public event ConnectionEventHandler OnDisconnect;
public event ConnectionDataEventHandler DataReceived;
protected static readonly Logger Logger = LogManager.CreateLogger("S");
protected static readonly Logger Logger = LogManager.CreateLogger();
private static bool _disposed = false;
public virtual void Run() { }
@ -43,7 +43,7 @@ namespace DiIiS_NA.GameServer.ClientSystem.Base
Acceptor = new AsyncAcceptor();
if (!Acceptor.Start(bindIP, port))
{
Logger.Fatal("Listen failed to Start AsyncAcceptor on {0}", bindIP);
Logger.Fatal("Listen failed to Start AsyncAcceptor on {0}:{1}", bindIP, port);
return false;
}

View File

@ -34,7 +34,7 @@ namespace DiIiS_NA.GameServer.ClientSystem
public void Consume(GameClient client, GameMessage message)
{
if (message is JoinBNetGameMessage) OnJoinGame(client, (JoinBNetGameMessage)message);
if (message is JoinBNetGameMessage gameMessage) OnJoinGame(client, gameMessage);
}
public void OnJoinGame(GameClient client, JoinBNetGameMessage message)

View File

@ -33,10 +33,10 @@ namespace DiIiS_NA.GameServer.ClientSystem
public override void Run()
{
int Port = 2001;
int port = 2001;
if (!Listen(Program.GameServerIp, Port)) return;
Logger.Info("Game Server Started - {0}:{1}...", Program.GameServerIp, Port);
if (!Listen(Program.GameServerIp, port)) return;
Logger.Info("Game Server Started - {0}:{1}...", Program.GameServerIp, port);
}
}
}

View File

@ -119,7 +119,7 @@ namespace DiIiS_NA.GameServer.CommandManager
output = $"Unknown command.";
#endif
if (output == string.Empty)
if (string.IsNullOrEmpty(output))
return true;
if (output.Contains("\n"))

View File

@ -0,0 +1,18 @@
using DiIiS_NA.Core.Logging;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.LoginServer.AccountsSystem;
using DiIiS_NA.LoginServer.Battle;
namespace DiIiS_NA.GameServer.CommandManager;
[CommandGroup("game", "Game Commands", Account.UserLevels.Admin, inGameOnly: false)]
public class GameCommand : CommandGroup
{
private static readonly Logger Logger = LogManager.CreateLogger();
[Command("reload-mods", "Reload all game mods file.", Account.UserLevels.Admin, inGameOnly: false)]
public void ReloadMods(string[] @params, BattleClient invokerClient)
{
GameModsConfig.ReloadSettings();
invokerClient?.SendServerWhisper("Game mods updated successfully!");
}
}

View File

@ -37,6 +37,7 @@ public class SpeedCommand : CommandGroup
if (speedValue <= baseSpeed) // Base Run Speed [Necrosummon]
{
playerSpeed[GameAttributes.Running_Rate] = baseSpeed;
playerSpeed.BroadcastChangedIfRevealed();
return $"Speed reset to Base Speed ({baseSpeed:0.000}).";
}
playerSpeed.FixedMap.Add(FixedAttribute.Speed, attr => attr[GameAttributes.Running_Rate] = speedValue);

View File

@ -1,4 +1,5 @@
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Hireling;
using System;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Hireling;
using DiIiS_NA.LoginServer.AccountsSystem;
using DiIiS_NA.LoginServer.Battle;

View File

@ -281,7 +281,7 @@ namespace DiIiS_NA.GameServer.Core
if (EquipmentSlot == 15)
ownerPlayer.Inventory.SaveItemToDB(ownerPlayer.Toon.GameAccount.DBGameAccount, null, EquipmentSlotId.Stash, item);
else
ownerPlayer.Inventory.SaveItemToDB(ownerPlayer.Toon.GameAccount.DBGameAccount, ownerPlayer.Toon.DBToon, EquipmentSlotId.Inventory, item);
ownerPlayer.Inventory.SaveItemToDB(ownerPlayer.Toon.GameAccount.DBGameAccount, ownerPlayer.Toon.DbToon, EquipmentSlotId.Inventory, item);
}
}

View File

@ -12,25 +12,13 @@ namespace DiIiS_NA.GameServer.Core.Types.Math
private static Vector2F _unitX;
private static Vector2F _unitY;
public static Vector2F Zero
{
get { return _zero; }
}
public static Vector2F Zero => _zero;
public static Vector2F One
{
get { return _one; }
}
public static Vector2F One => _one;
public static Vector2F UnitX
{
get { return _unitX; }
}
public static Vector2F UnitX => _unitX;
public static Vector2F UnitY
{
get { return _unitY; }
}
public static Vector2F UnitY => _unitY;
public Vector2F(float x, float y)
{

View File

@ -7,6 +7,9 @@ using DiIiS_NA.Core.Storage;
using System;
using System.Numerics;
using DiIiS_NA.Core.Helpers.Math;
using System.Collections.Generic;
using System.Linq;
using DiIiS_NA.GameServer.GSSystem.ActorSystem;
namespace DiIiS_NA.GameServer.Core.Types.Math
{
@ -113,6 +116,36 @@ namespace DiIiS_NA.GameServer.Core.Types.Math
return ((x * x) + (y * y)) + (z * z);
}
public static double Distance(Vector3D vector1, Vector3D vector2)
{
return ((vector1.X * vector2.X) + (vector1.Y * vector2.Y) + (vector1.Z * vector2.Z));
}
public static bool IsInDistanceSquared(Vector3D position, Vector3D relative, double distanceMax, double distanceMin = -1f)
{
var dist = Distance(position, relative);
return dist < distanceMax && dist > distanceMin;
}
private static Random rand = new Random();
public Vector3D Around(float radius)
{
return Around(radius, radius, radius);
}
public Vector3D Around(float x, float y, float z)
{
float newX = X + ((float)rand.NextDouble() * 2 * x) - x;
float newY = Y + ((float)rand.NextDouble() * 2 * y) - y;
float newZ = Z + ((float)rand.NextDouble() * 2 * z) - z;
return new Vector3D(newX, newY, newZ);
}
public Vector3D Around(Vector3D vector)
{
return Around(vector.X, vector.Y, vector.Z);
}
public static bool operator ==(Vector3D a, Vector3D b) => a?.Equals(b) ?? ReferenceEquals(null, b);
public static bool operator !=(Vector3D a, Vector3D b) => !(a == b);
@ -167,5 +200,34 @@ namespace DiIiS_NA.GameServer.Core.Types.Math
public override string ToString() => $"X:{X:F4}, Y:{Y:F4} Z:{Z:F4}";
public bool IsNear(Vector3D other, float distance) => DistanceSquared(ref other) < distance;
}
public override int GetHashCode() => HashCode.Combine(X.ToDouble(decimals: 6), Y.ToDouble(decimals: 6), Z.ToDouble(decimals: 6));
}
public static class VectorExtensions
{
/// <summary>
/// Takes all actors from the given collection that are within the specified distance of the reference position.
/// </summary>
/// <typeparam name="TActor"></typeparam>
/// <param name="actors"></param>
/// <param name="referencePosition"></param>
/// <param name="query"></param>
/// <returns></returns>
public static TSearch[] WhereNearbyOf<TActor, TSearch>(
this IEnumerable<TActor> actors,
TSearch[] referenceActors,
Func<TSearch, bool> query,
double maxDistance = 50f,
double minDistance = 1f)
where TActor : Actor
where TSearch : Actor
{
return actors.OfType<TSearch>()
.Where(actor => query(actor))
.Where((actor, dist) =>
referenceActors.Any(refActor => refActor.GlobalID != actor.GlobalID && Vector3D.IsInDistanceSquared(refActor.Position, actor.Position, maxDistance, minDistance)))
.ToArray();
}
}
}

View File

@ -47,7 +47,7 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem
public virtual void Update(int tickCounter)
{
if (State == BrainState.Dead || Body == null || Body.World == null || State == BrainState.Off)
if (State == BrainState.Dead || Body?.World == null || State == BrainState.Off)
return;
Think(tickCounter); // let the brain think.

View File

@ -5,6 +5,7 @@ using DiIiS_NA.Core.Extensions;
using DiIiS_NA.Core.Helpers.Math;
using DiIiS_NA.Core.Logging;
using DiIiS_NA.Core.MPQ;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
using DiIiS_NA.GameServer.Core.Types.SNO;
using DiIiS_NA.GameServer.Core.Types.TagMap;
@ -21,456 +22,532 @@ using DiIiS_NA.GameServer.MessageSystem;
namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
{
public class MonsterBrain : Brain
{
private new readonly Logger Logger;
// list of power SNOs that are defined for the monster
public Dictionary<int, Cooldown> PresetPowers { get; private set; }
private TickTimer _powerDelay;
public struct Cooldown
{
public TickTimer CooldownTimer;
public float CooldownTime;
}
private bool _warnedNoPowers;
private Actor _target { get; set; }
private int _mpqPowerCount;
private bool Feared = false;
public Actor AttackedBy = null;
public TickTimer TimeoutAttacked = null;
public Actor PriorityTarget = null;
public MonsterBrain(Actor body)
: base(body)
{
Logger = LogManager.CreateLogger(GetType().Name);
PresetPowers = new Dictionary<int, Cooldown>();
// build list of powers defined in monster mpq data
if (body.ActorData.MonsterSNO <= 0)
{
Logger.Warn($"$[red]${GetType().Name}$[/]$ - Monster \"{body.SNO}\" has no monster SNO");
return;
}
var monsterData = (DiIiS_NA.Core.MPQ.FileFormats.Monster)MPQStorage.Data.Assets[SNOGroup.Monster][body.ActorData.MonsterSNO].Data;
_mpqPowerCount = monsterData.SkillDeclarations.Count(e => e.SNOPower != -1);
for (int i = 0; i < monsterData.SkillDeclarations.Length; i++)
{
if (monsterData.SkillDeclarations[i].SNOPower == -1) continue;
if (PowerLoader.HasImplementationForPowerSNO(monsterData.SkillDeclarations[i].SNOPower))
{
var cooldownTime = monsterData.MonsterSkillDeclarations[i].Timer / 10f;
PresetPowers.Add(monsterData.SkillDeclarations[i].SNOPower, new Cooldown { CooldownTimer = null, CooldownTime = cooldownTime });
}
}
if (monsterData.SkillDeclarations.All(s => s.SNOPower != 30592))
PresetPowers.Add(30592, new Cooldown { CooldownTimer = null, CooldownTime = 0f }); //hack for dummy mobs without powers
}
public override void Think(int tickCounter)
{
switch (Body.SNO)
{
case ActorSno._uber_siegebreakerdemon:
case ActorSno._a4dun_garden_corruption_monster:
case ActorSno._a4dun_garden_hellportal_pillar:
case ActorSno._belialvoiceover:
return;
}
if (Body.Hidden)
return;
if (CurrentAction != null && PriorityTarget != null && PriorityTarget.Attributes[GameAttributes.Is_Helper] == true)
{
PriorityTarget = null;
CurrentAction.Cancel(tickCounter);
CurrentAction = null;
return;
}
if (tickCounter % 60 != 0) return;
if (Body is NPC) return;
if (!Body.Visible || Body.Dead) return;
if (Body.World.Game.Paused) return;
if (Body.Attributes[GameAttributes.Disabled]) return;
if (Body.Attributes[GameAttributes.Frozen] ||
Body.Attributes[GameAttributes.Stunned] ||
Body.Attributes[GameAttributes.Blind] ||
Body.Attributes[GameAttributes.Webbed] ||
Body.Disable ||
Body.World.BuffManager.GetFirstBuff<KnockbackBuff>(Body) != null ||
Body.World.BuffManager.GetFirstBuff<SummonedBuff>(Body) != null)
{
if (CurrentAction != null)
{
CurrentAction.Cancel(tickCounter);
CurrentAction = null;
}
_powerDelay = null;
return;
}
if (Body.Attributes[GameAttributes.Feared])
{
if (!Feared || CurrentAction == null)
{
if (CurrentAction != null)
{
CurrentAction.Cancel(tickCounter);
CurrentAction = null;
}
Feared = true;
CurrentAction = new MoveToPointWithPathfindAction(
Body,
PowerContext.RandomDirection(Body.Position, 3f, 8f)
);
return;
}
return;
}
Feared = false;
if (CurrentAction == null)
{
_powerDelay ??= new SecondsTickTimer(Body.World.Game, 1.0f);
if (AttackedBy != null || Body.GetObjectsInRange<Player>(50f).Count != 0)
{
if (_powerDelay.TimedOut)
{
_powerDelay = new SecondsTickTimer(Body.World.Game, 1.0f);
if (AttackedBy != null)
PriorityTarget = AttackedBy;
if (PriorityTarget == null)
{
Actor[] targets;
if (Body.Attributes[GameAttributes.Team_Override] == 1)
targets = Body.GetObjectsInRange<Monster>(60f)
.Where(p => !p.Dead)
.OrderBy((monster) => PowerMath.Distance2D(monster.Position, Body.Position))
.ToArray();
else
targets = Body.GetActorsInRange(50f)
.Where(p => ((p is Player) && !p.Dead && p.Attributes[GameAttributes.Loading] == false && p.Attributes[GameAttributes.Is_Helper] == false && p.World.BuffManager.GetFirstBuff<ActorGhostedBuff>(p) == null)
|| ((p is Minion) && !p.Dead && p.Attributes[GameAttributes.Is_Helper] == false)
|| (p is DesctructibleLootContainer && p.SNO.IsDoorOrBarricade())
|| ((p is Hireling) && !p.Dead)
)
.OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
.ToArray();
if (targets.Length == 0) return;
_target = targets.First();
}
else
_target = PriorityTarget;
int powerToUse = PickPowerToUse();
if (powerToUse > 0)
{
PowerScript power = PowerLoader.CreateImplementationForPowerSNO(powerToUse);
power.User = Body;
float attackRange = Body.ActorData.Cylinder.Ax2 + (power.EvalTag(PowerKeys.AttackRadius) > 0f ? (powerToUse == 30592 ? 10f : Math.Min((float)power.EvalTag(PowerKeys.AttackRadius), 35f)) : 35f);
float targetDistance = PowerMath.Distance2D(_target.Position, Body.Position);
if (targetDistance < attackRange + _target.ActorData.Cylinder.Ax2)
{
if (Body.WalkSpeed != 0)
Body.TranslateFacing(_target.Position, false);
CurrentAction = new PowerAction(Body, powerToUse, _target);
if (power is SummoningSkill)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f) };
if (power is MonsterAffixSkill monsterAffixSkill)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = monsterAffixSkill.CooldownTime };
if (PresetPowers[powerToUse].CooldownTime > 0f)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, PresetPowers[powerToUse].CooldownTime), CooldownTime = PresetPowers[powerToUse].CooldownTime };
if (powerToUse is 96925 or 223284)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, 10f), CooldownTime = 10f };
}
else if (Body.WalkSpeed != 0)
{
if (Body.SNO.IsWoodwraithOrWasp())
{
Logger.Trace($"{GetType().Name} $[underline white]${nameof(MoveToPointAction)}$[/]$ to target $[white]${_target.ActorType}$[/]$ [{_target.Position}]");
CurrentAction = new MoveToPointAction(
Body, _target.Position
);
}
else
{
Logger.Trace($"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{_target.ActorType}] {_target.SNO.ToString()}");
CurrentAction = new MoveToTargetWithPathfindAction(
Body,
_target,
attackRange + _target.ActorData.Cylinder.Ax2,
powerToUse
);
}
}
else
{
powerToUse = Body.SNO switch
{
ActorSno._a1dun_leor_firewall2 => 223284,
_ => powerToUse
};
CurrentAction = new PowerAction(Body, powerToUse, _target);
if (power is SummoningSkill)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f) };
if (power is MonsterAffixSkill)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = (power as MonsterAffixSkill).CooldownTime };
if (PresetPowers[powerToUse].CooldownTime > 0f)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, PresetPowers[powerToUse].CooldownTime), CooldownTime = PresetPowers[powerToUse].CooldownTime };
if (powerToUse == 96925 ||
powerToUse == 223284)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, 10f), CooldownTime = 10f };
}
}
}
}
else if (Body.GetObjectsInRange<Living>(50f).Count != 0)
{
if (_powerDelay.TimedOut)
{
_powerDelay = new SecondsTickTimer(Body.World.Game, 1.0f);
if (AttackedBy != null)
PriorityTarget = AttackedBy;
if (PriorityTarget == null)
{
var targets = Body.GetActorsInRange(50f)
.Where(p => ((p is LorathNahr_NPC) && !p.Dead)
|| ((p is CaptainRumford) && !p.Dead)
|| (p is DesctructibleLootContainer && p.SNO.IsDoorOrBarricade())
|| ((p is Cain) && !p.Dead))
.OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
.ToArray();
if (targets.Length == 0)
{
targets = Body.GetActorsInRange(20f)
.Where(p => ((p is Monster) && !p.Dead)
|| ((p is CaptainRumford) && !p.Dead)
)
.OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
.ToArray();
if (targets.Length == 0)
return;
foreach (var monsterActor in targets.Where(tar => _target == null))
if (monsterActor is Monster { Brain: MonsterBrain brain } monster && monsterActor != Body)
if (brain.AttackedBy != null)
_target = brain.AttackedBy;
}
else
{
_target = targets.First();
}
foreach (var tar in targets)
if (tar is DesctructibleLootContainer && tar.SNO.IsDoorOrBarricade() && tar.SNO != ActorSno._trout_wagon_barricade)
{ _target = tar; break; }
}
else
_target = PriorityTarget;
int powerToUse = PickPowerToUse();
if (powerToUse > 0)
{
PowerScript power = PowerLoader.CreateImplementationForPowerSNO(powerToUse);
power.User = Body;
if (_target == null)
{
/*
if (!this.Body.ActorSNO.Name.ToLower().Contains("woodwraith") &&
!this.Body.ActorSNO.Name.ToLower().Contains("wasp"))
if (this.Body.Quality < 2)
{
this.CurrentAction = new MoveToPointWithPathfindAction(this.Body, RandomPosibleDirection(this.Body.CheckPointPosition, 3f, 8f, this.Body.World));
return;
}
else
//*/
return;
}
float attackRange = Body.ActorData.Cylinder.Ax2 + (power.EvalTag(PowerKeys.AttackRadius) > 0f ? (powerToUse == 30592 ? 10f : Math.Min((float)power.EvalTag(PowerKeys.AttackRadius), 35f)) : 35f);
float targetDistance = PowerMath.Distance2D(_target.Position, Body.Position);
if (targetDistance < attackRange + _target.ActorData.Cylinder.Ax2)
{
if (Body.WalkSpeed != 0)
Body.TranslateFacing(_target.Position, false); //columns and other non-walkable shit can't turn
Logger.Trace($"{GetType().Name} {nameof(PowerAction)} to target [{_target.ActorType}] {_target.SNO.ToString()}");
// Logger.Trace("PowerAction to target");
CurrentAction = new PowerAction(Body, powerToUse, _target);
if (power is SummoningSkill)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f) };
if (power is MonsterAffixSkill monsterSkill)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = monsterSkill.CooldownTime };
if (PresetPowers[powerToUse].CooldownTime > 0f)
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, PresetPowers[powerToUse].CooldownTime), CooldownTime = PresetPowers[powerToUse].CooldownTime };
}
else if (Body.WalkSpeed != 0)
{
if (Body.SNO.IsWoodwraithOrWasp())
{
Logger.Trace($"{GetType().Name} {nameof(MoveToPointAction)} to target [{_target.Position}]");
CurrentAction = new MoveToPointAction(
Body, _target.Position
);
}
else
{
Logger.Trace($"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{_target.ActorType}] {_target.SNO.ToString()}");
CurrentAction = new MoveToTargetWithPathfindAction(
Body,
//(
_target,// + MovementHelpers.GetMovementPosition(
//new Vector3D(0, 0, 0),
//this.Body.WalkSpeed,
//MovementHelpers.GetFacingAngle(_target.Position, this.Body.Position),
//6
//)
//)
attackRange + _target.ActorData.Cylinder.Ax2,
powerToUse
);
}
}
}
}
}
else
{
//Logger.Trace("No enemies in range, return to master");
if (Body.Position != Body.CheckPointPosition)
CurrentAction = new MoveToPointWithPathfindAction(Body, Body.CheckPointPosition);
}
}
}
public static Core.Types.Math.Vector3D RandomPossibleDirection(Core.Types.Math.Vector3D position, float minRadius, float maxRadius, MapSystem.World world)
{
float angle = (float)(FastRandom.Instance.NextDouble() * Math.PI * 2);
float radius = minRadius + (float)FastRandom.Instance.NextDouble() * (maxRadius - minRadius);
Core.Types.Math.Vector3D point = null;
int tryC = 0;
while (tryC < 100)
{
//break;
point = new Core.Types.Math.Vector3D(position.X + (float)Math.Cos(angle) * radius,
position.Y + (float)Math.Sin(angle) * radius,
position.Z);
if (world.CheckLocationForFlag(point, DiIiS_NA.Core.MPQ.FileFormats.Scene.NavCellFlags.AllowWalk))
break;
tryC++;
}
return point;
}
public void FastAttack(Actor target, int skillSNO)
{
PowerScript power = PowerLoader.CreateImplementationForPowerSNO(skillSNO);
power.User = Body;
if (Body.WalkSpeed != 0)
Body.TranslateFacing(target.Position, false); //columns and other non-walkable shit can't turn
Logger.Trace($"{GetType().Name} {nameof(FastAttack)} {nameof(PowerAction)} to target [{_target.ActorType}] {_target.SNO.ToString()}");
CurrentAction = new PowerAction(Body, skillSNO, target);
if (power is SummoningSkill)
PresetPowers[skillSNO] = new Cooldown { CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f) };
if (power is MonsterAffixSkill monsterAffixSkill)
PresetPowers[skillSNO] = new Cooldown { CooldownTimer = null, CooldownTime = monsterAffixSkill.CooldownTime };
if (PresetPowers[skillSNO].CooldownTime > 0f)
PresetPowers[skillSNO] = new Cooldown { CooldownTimer = new SecondsTickTimer(Body.World.Game, PresetPowers[skillSNO].CooldownTime), CooldownTime = PresetPowers[skillSNO].CooldownTime };
}
protected virtual int PickPowerToUse()
{
if (!_warnedNoPowers && PresetPowers.Count == 0)
{
Logger.Warn($"Monster $[red]$\"{Body.Name}\"$[/]$ has no usable powers. {_mpqPowerCount} are defined in mpq data.");
_warnedNoPowers = true;
return -1;
}
// randomly used an implemented power
if (PresetPowers.Count <= 0) return -1;
//int power = this.PresetPowers[RandomHelper.Next(this.PresetPowers.Count)].Key;
var availablePowers = PresetPowers.Where(p => (p.Value.CooldownTimer == null || p.Value.CooldownTimer.TimedOut) && PowerLoader.HasImplementationForPowerSNO(p.Key)).Select(p => p.Key).ToList();
if (availablePowers.Where(p => p != 30592).TryPickRandom(out var selectedPower))
{
return selectedPower;
}
if (availablePowers.Contains(30592))
return 30592; // melee attack
// no usable power
return -1;
}
public void AddPresetPower(int powerSNO)
{
if (PresetPowers.ContainsKey(powerSNO))
{
Logger.Debug($"Monster $[red]$\"{Body.Name}\"$[/]$ already has power {powerSNO}.");
// Logger.MethodTrace("power sno {0} already defined for monster \"{1}\"",
//powerSNO, this.Body.ActorSNO.Name);
return;
}
PresetPowers.Add(powerSNO,
PresetPowers.ContainsKey(30592) //if can cast melee
? new Cooldown { CooldownTimer = null, CooldownTime = 5f }
: new Cooldown
{ CooldownTimer = null, CooldownTime = 1f + (float)FastRandom.Instance.NextDouble() });
}
public void RemovePresetPower(int powerSNO)
{
if (PresetPowers.ContainsKey(powerSNO))
{
PresetPowers.Remove(powerSNO);
}
}
}
public class MonsterBrain : Brain
{
private readonly Logger _logger;
// list of power SNOs that are defined for the monster
public Dictionary<int, Cooldown> PresetPowers { get; private set; }
private TickTimer _powerDelay;
public struct Cooldown
{
public TickTimer CooldownTimer;
public float CooldownTime;
}
private bool _warnedNoPowers;
private Actor Target { get; set; }
private readonly int _mpqPowerCount;
private bool _feared = false;
public Actor AttackedBy = null;
public TickTimer TimeoutAttacked = null;
public Actor PriorityTarget = null;
public MonsterBrain(Actor body)
: base(body)
{
_logger = LogManager.CreateLogger(GetType().Name);
PresetPowers = new Dictionary<int, Cooldown>();
// build list of powers defined in monster mpq data
if (body.ActorData.MonsterSNO <= 0)
{
_logger.Warn($"$[red]${GetType().Name}$[/]$ - Monster \"{body.SNO}\" has no monster SNO");
return;
}
var monsterData =
(DiIiS_NA.Core.MPQ.FileFormats.Monster)MPQStorage.Data.Assets[SNOGroup.Monster][
body.ActorData.MonsterSNO].Data;
_mpqPowerCount = monsterData.SkillDeclarations.Count(e => e.SNOPower != -1);
for (int i = 0; i < monsterData.SkillDeclarations.Length; i++)
{
if (monsterData.SkillDeclarations[i].SNOPower == -1) continue;
if (!PowerLoader.HasImplementationForPowerSNO(monsterData.SkillDeclarations[i].SNOPower)) continue;
var cooldownTime = monsterData.MonsterSkillDeclarations[i].Timer / 10f;
PresetPowers.Add(monsterData.SkillDeclarations[i].SNOPower,
new Cooldown { CooldownTimer = null, CooldownTime = cooldownTime });
}
if (monsterData.SkillDeclarations.All(s => s.SNOPower != 30592))
PresetPowers.Add(30592,
new Cooldown { CooldownTimer = null, CooldownTime = 0f }); //hack for dummy mobs without powers
}
public override void Think(int tickCounter)
{
switch (Body.SNO)
{
case ActorSno._uber_siegebreakerdemon:
case ActorSno._a4dun_garden_corruption_monster:
case ActorSno._a4dun_garden_hellportal_pillar:
case ActorSno._belialvoiceover:
return;
}
if (Body.Hidden)
return;
if (CurrentAction != null && PriorityTarget != null &&
PriorityTarget.Attributes[GameAttributes.Is_Helper] == true)
{
PriorityTarget = null;
CurrentAction.Cancel(tickCounter);
CurrentAction = null;
return;
}
if (tickCounter % 60 != 0) return;
if (Body is NPC) return;
if (!Body.Visible || Body.Dead) return;
if (Body.World.Game.Paused) return;
if (Body.Attributes[GameAttributes.Disabled]) return;
if (Body.Attributes[GameAttributes.Frozen] ||
Body.Attributes[GameAttributes.Stunned] ||
Body.Attributes[GameAttributes.Blind] ||
Body.Attributes[GameAttributes.Webbed] ||
Body.Disable ||
Body.World.BuffManager.GetFirstBuff<KnockbackBuff>(Body) != null ||
Body.World.BuffManager.GetFirstBuff<SummonedBuff>(Body) != null)
{
if (CurrentAction != null)
{
CurrentAction.Cancel(tickCounter);
CurrentAction = null;
}
_powerDelay = null;
return;
}
if (Body.Attributes[GameAttributes.Feared])
{
if (_feared && CurrentAction != null) return;
if (CurrentAction != null)
{
CurrentAction.Cancel(tickCounter);
CurrentAction = null;
}
_feared = true;
CurrentAction = new MoveToPointWithPathfindAction(
Body,
PowerContext.RandomDirection(Body.Position, 3f, 8f)
);
return;
}
_feared = false;
if (CurrentAction != null) return;
_powerDelay ??= new SecondsTickTimer(Body.World.Game, 1.0f);
// Check if the character has been attacked or if there are any players within 50 units range
if (AttackedBy != null || Body.GetObjectsInRange<Player>(GameModsConfig.Instance.Monster.LookupRange).Count != 0)
{
// If the power delay hasn't timed out, return
if (!_powerDelay.TimedOut) return;
// Reset the power delay
_powerDelay = new SecondsTickTimer(Body.World.Game, 1.0f);
// If the character has been attacked, set the attacker as the priority target
if (AttackedBy != null)
PriorityTarget = AttackedBy;
// If there's no defined priority target, start a search
if (PriorityTarget == null)
{
Actor[] targets;
// If the character is part of a team, search for alive monsters within a range of 60 units and order them by distance
if (Body.Attributes[GameAttributes.Team_Override] == 1)
targets = Body.GetObjectsInRange<Monster>(GameModsConfig.Instance.Monster.LookupRange +10f)
.Where(p => !p.Dead)
.OrderBy((monster) => PowerMath.Distance2D(monster.Position, Body.Position))
.ToArray();
else
// Otherwise, search for different types of actors including players, minions, destructible loot containers, or hirelings that are alive, not loading and not helpers, and order them by distance
targets = Body.GetActorsInRange(GameModsConfig.Instance.Monster.LookupRange)
.Where(p => ((p is Player) && !p.Dead && p.Attributes[GameAttributes.Loading] == false &&
p.Attributes[GameAttributes.Is_Helper] == false &&
p.World.BuffManager.GetFirstBuff<ActorGhostedBuff>(p) == null)
|| ((p is Minion) && !p.Dead && p.Attributes[GameAttributes.Is_Helper] == false)
|| (p is DesctructibleLootContainer && p.SNO.IsDoorOrBarricade())
|| ((p is Hireling) && !p.Dead)
)
.OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
.ToArray();
// If there are no targets, return
if (targets.Length == 0) return;
// Set the first found target as the target
Target = targets.First();
}
else
// If there is a priority target, set it as the target
Target = PriorityTarget;
int powerToUse = PickPowerToUse();
if (powerToUse <= 0) return;
PowerScript power = PowerLoader.CreateImplementationForPowerSNO(powerToUse);
power.User = Body;
float attackRange = Body.ActorData.Cylinder.Ax2 + (power.EvalTag(PowerKeys.AttackRadius) > 0f
? (powerToUse == 30592 ? 10f : Math.Min((float)power.EvalTag(PowerKeys.AttackRadius), 35f))
: 35f);
float targetDistance = PowerMath.Distance2D(Target.Position, Body.Position);
if (targetDistance < attackRange + Target.ActorData.Cylinder.Ax2)
{
if (Body.WalkSpeed != 0)
Body.TranslateFacing(Target.Position, false);
CurrentAction = new PowerAction(Body, powerToUse, Target);
PresetPowers[powerToUse] = power switch
{
SummoningSkill => new Cooldown
{
CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f)
},
MonsterAffixSkill monsterAffixSkill => new Cooldown
{
CooldownTimer = null, CooldownTime = monsterAffixSkill.CooldownTime
},
_ => PresetPowers[powerToUse]
};
if (PresetPowers[powerToUse].CooldownTime > 0f)
PresetPowers[powerToUse] = new Cooldown
{
CooldownTimer =
new SecondsTickTimer(Body.World.Game, PresetPowers[powerToUse].CooldownTime),
CooldownTime = PresetPowers[powerToUse].CooldownTime
};
if (powerToUse is 96925 or 223284)
PresetPowers[powerToUse] = new Cooldown
{ CooldownTimer = new SecondsTickTimer(Body.World.Game, 10f), CooldownTime = 10f };
}
else if (Body.WalkSpeed != 0)
{
if (Body.SNO.IsWoodwraithOrWasp())
{
_logger.Trace(
$"{GetType().Name} $[underline white]${nameof(MoveToPointAction)}$[/]$ to target $[white]${Target.ActorType}$[/]$ [{Target.Position}]");
CurrentAction = new MoveToPointAction(
Body, Target.Position
);
}
else
{
_logger.Trace(
$"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
CurrentAction = new MoveToTargetWithPathfindAction(
Body,
Target,
attackRange + Target.ActorData.Cylinder.Ax2,
powerToUse
);
}
}
else
{
powerToUse = Body.SNO switch
{
ActorSno._a1dun_leor_firewall2 => 223284,
_ => powerToUse
};
CurrentAction = new PowerAction(Body, powerToUse, Target);
PresetPowers[powerToUse] = power switch
{
SummoningSkill => new Cooldown
{
CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f)
},
MonsterAffixSkill skill => new Cooldown
{
CooldownTimer = null, CooldownTime = skill.CooldownTime
},
_ => PresetPowers[powerToUse]
};
if (PresetPowers[powerToUse].CooldownTime > 0f)
PresetPowers[powerToUse] = new Cooldown
{
CooldownTimer =
new SecondsTickTimer(Body.World.Game, PresetPowers[powerToUse].CooldownTime),
CooldownTime = PresetPowers[powerToUse].CooldownTime
};
if (powerToUse is 96925 or 223284)
PresetPowers[powerToUse] = new Cooldown
{ CooldownTimer = new SecondsTickTimer(Body.World.Game, 10f), CooldownTime = 10f };
}
}
else if (Body.GetObjectsInRange<Living>(GameModsConfig.Instance.Monster.LookupRange).Count != 0)
{
if (!_powerDelay.TimedOut) return;
_powerDelay = new SecondsTickTimer(Body.World.Game, 1.0f);
if (AttackedBy != null)
PriorityTarget = AttackedBy;
if (PriorityTarget == null)
{
var targets = Body.GetActorsInRange(GameModsConfig.Instance.Monster.LookupRange)
.Where(p => ((p is LorathNahr_NPC) && !p.Dead)
|| ((p is CaptainRumford) && !p.Dead)
|| (p is DesctructibleLootContainer && p.SNO.IsDoorOrBarricade())
|| ((p is Cain) && !p.Dead))
.OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
.ToArray();
if (targets.Length == 0)
{
targets = Body.GetActorsInRange(20f)
.Where(p => ((p is Monster) && !p.Dead)
|| ((p is CaptainRumford) && !p.Dead)
)
.OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
.ToArray();
if (targets.Length == 0)
return;
foreach (var monsterActor in targets.Where(tar => Target == null))
if (monsterActor is Monster { Brain: MonsterBrain brain } monster && monsterActor != Body)
if (brain.AttackedBy != null)
Target = brain.AttackedBy;
}
else
{
Target = targets.First();
}
foreach (var tar in targets)
if (tar is DesctructibleLootContainer && tar.SNO.IsDoorOrBarricade() &&
tar.SNO != ActorSno._trout_wagon_barricade)
{
Target = tar;
break;
}
}
else
Target = PriorityTarget;
int powerToUse = PickPowerToUse();
if (powerToUse > 0)
{
PowerScript power = PowerLoader.CreateImplementationForPowerSNO(powerToUse);
power.User = Body;
if (Target == null)
{
/*
if (!this.Body.ActorSNO.Name.ToLower().Contains("woodwraith") &&
!this.Body.ActorSNO.Name.ToLower().Contains("wasp"))
if (this.Body.Quality < 2)
{
this.CurrentAction = new MoveToPointWithPathfindAction(this.Body, RandomPosibleDirection(this.Body.CheckPointPosition, 3f, 8f, this.Body.World));
return;
}
else
//*/
return;
}
float attackRange = Body.ActorData.Cylinder.Ax2 + (power.EvalTag(PowerKeys.AttackRadius) > 0f
? (powerToUse == 30592 ? 10f : Math.Min((float)power.EvalTag(PowerKeys.AttackRadius), 35f))
: 35f);
float targetDistance = PowerMath.Distance2D(Target.Position, Body.Position);
if (targetDistance < attackRange + Target.ActorData.Cylinder.Ax2)
{
if (Body.WalkSpeed != 0)
Body.TranslateFacing(Target.Position,
false); //columns and other non-walkable shit can't turn
_logger.Trace(
$"{GetType().Name} {nameof(PowerAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
// Logger.Trace("PowerAction to target");
CurrentAction = new PowerAction(Body, powerToUse, Target);
PresetPowers[powerToUse] = power switch
{
SummoningSkill => new Cooldown
{
CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f)
},
MonsterAffixSkill monsterSkill => new Cooldown
{
CooldownTimer = null, CooldownTime = monsterSkill.CooldownTime
},
_ => PresetPowers[powerToUse]
};
if (PresetPowers[powerToUse].CooldownTime > 0f)
PresetPowers[powerToUse] = new Cooldown
{
CooldownTimer = new SecondsTickTimer(Body.World.Game,
PresetPowers[powerToUse].CooldownTime),
CooldownTime = PresetPowers[powerToUse].CooldownTime
};
}
else if (Body.WalkSpeed != 0)
{
if (Body.SNO.IsWoodwraithOrWasp())
{
_logger.Trace(
$"{GetType().Name} {nameof(MoveToPointAction)} to target [{Target.Position}]");
CurrentAction = new MoveToPointAction(
Body, Target.Position
);
}
else
{
_logger.Trace(
$"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
CurrentAction = new MoveToTargetWithPathfindAction(
Body,
//(
Target, // + MovementHelpers.GetMovementPosition(
//new Vector3D(0, 0, 0),
//this.Body.WalkSpeed,
//MovementHelpers.GetFacingAngle(_target.Position, this.Body.Position),
//6
//)
//)
attackRange + Target.ActorData.Cylinder.Ax2,
powerToUse
);
}
}
}
}
else
{
//Logger.Trace("No enemies in range, return to master");
if (Body.Position != Body.CheckPointPosition)
CurrentAction = new MoveToPointWithPathfindAction(Body, Body.CheckPointPosition);
}
}
public static Core.Types.Math.Vector3D RandomPossibleDirection(Core.Types.Math.Vector3D position,
float minRadius, float maxRadius, MapSystem.World world)
{
float angle = (float)(FastRandom.Instance.NextDouble() * Math.PI * 2);
float radius = minRadius + (float)FastRandom.Instance.NextDouble() * (maxRadius - minRadius);
Core.Types.Math.Vector3D point = null;
int tryC = 0;
while (tryC < 100)
{
//break;
point = new Core.Types.Math.Vector3D(position.X + (float)Math.Cos(angle) * radius,
position.Y + (float)Math.Sin(angle) * radius,
position.Z);
if (world.CheckLocationForFlag(point, DiIiS_NA.Core.MPQ.FileFormats.Scene.NavCellFlags.AllowWalk))
break;
tryC++;
}
return point;
}
public void FastAttack(Actor target, int skillSno)
{
PowerScript power = PowerLoader.CreateImplementationForPowerSNO(skillSno);
power.User = Body;
if (Body.WalkSpeed != 0)
Body.TranslateFacing(target.Position, false); //columns and other non-walkable shit can't turn
_logger.Trace(
$"{GetType().Name} {nameof(FastAttack)} {nameof(PowerAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
CurrentAction = new PowerAction(Body, skillSno, target);
PresetPowers[skillSno] = power switch
{
SummoningSkill => new Cooldown { CooldownTimer = null, CooldownTime = (Body is Boss ? 15f : 7f) },
MonsterAffixSkill monsterAffixSkill => new Cooldown
{
CooldownTimer = null, CooldownTime = monsterAffixSkill.CooldownTime
},
_ => PresetPowers[skillSno]
};
if (PresetPowers[skillSno].CooldownTime > 0f)
PresetPowers[skillSno] = new Cooldown
{
CooldownTimer = new SecondsTickTimer(Body.World.Game, PresetPowers[skillSno].CooldownTime),
CooldownTime = PresetPowers[skillSno].CooldownTime
};
}
protected virtual int PickPowerToUse()
{
if (!_warnedNoPowers && PresetPowers.Count == 0)
{
_logger.Warn(
$"Monster $[red]$\"{Body.Name}\"$[/]$ has no usable powers. {_mpqPowerCount} are defined in mpq data.");
_warnedNoPowers = true;
return -1;
}
// randomly used an implemented power
if (PresetPowers.Count <= 0) return -1;
//int power = this.PresetPowers[RandomHelper.Next(this.PresetPowers.Count)].Key;
var availablePowers = PresetPowers
.Where(p => (p.Value.CooldownTimer == null || p.Value.CooldownTimer.TimedOut) &&
PowerLoader.HasImplementationForPowerSNO(p.Key)).Select(p => p.Key).ToList();
if (availablePowers.Where(p => p != 30592).TryPickRandom(out var selectedPower))
{
return selectedPower;
}
if (availablePowers.Contains(30592))
return 30592; // melee attack
// no usable power
return -1;
}
public void AddPresetPower(int powerSno)
{
if (PresetPowers.ContainsKey(powerSno))
{
_logger.Debug($"Monster $[red]$\"{Body.Name}\"$[/]$ already has power {powerSno}.");
// Logger.MethodTrace("power sno {0} already defined for monster \"{1}\"",
//powerSNO, this.Body.ActorSNO.Name);
return;
}
PresetPowers.Add(powerSno,
PresetPowers.ContainsKey(30592) //if can cast melee
? new Cooldown { CooldownTimer = null, CooldownTime = 5f }
: new Cooldown
{ CooldownTimer = null, CooldownTime = 1f + (float)FastRandom.Instance.NextDouble() });
}
public void RemovePresetPower(int powerSno)
{
if (PresetPowers.ContainsKey(powerSno))
{
PresetPowers.Remove(powerSno);
}
}
}
}

View File

@ -4,6 +4,7 @@ using DiIiS_NA.GameServer.GSSystem.AISystem.Brains;
using DiIiS_NA.GameServer.MessageSystem;
using DiIiS_NA.Core.Logging;
using DiIiS_NA.Core.MPQ.FileFormats;
using DiIiS_NA.D3_GameServer;
namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations
{
@ -73,9 +74,9 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations
//this.Attributes[GameAttribute.Immune_To_Charm] = true;
Attributes[GameAttributes.using_Bossbar] = true;
Attributes[GameAttributes.InBossEncounter] = true;
Attributes[GameAttributes.Hitpoints_Max] *= GameServerConfig.Instance.BossHealthMultiplier;
Attributes[GameAttributes.Damage_Weapon_Min, 0] *= GameServerConfig.Instance.BossDamageMultiplier;
Attributes[GameAttributes.Damage_Weapon_Delta, 0] *= GameServerConfig.Instance.BossDamageMultiplier;
Attributes[GameAttributes.Hitpoints_Max] *= GameModsConfig.Instance.Boss.HealthMultiplier;
Attributes[GameAttributes.Damage_Weapon_Min, 0] *= GameModsConfig.Instance.Boss.DamageMultiplier;
Attributes[GameAttributes.Damage_Weapon_Delta, 0] *= GameModsConfig.Instance.Boss.DamageMultiplier;
Attributes[GameAttributes.Hitpoints_Cur] = Attributes[GameAttributes.Hitpoints_Max_Total];
Attributes[GameAttributes.TeamID] = 10;

View File

@ -52,7 +52,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations
World.SpawnRandomEquip(player, player,
FastRandom.Instance.Next(100) < chance ? LootManager.Epic : LootManager.Rare, player.Level);
var toon = player.Toon.DBToon;
var toon = player.Toon.DbToon;
toon.ChestsOpened++;
World.Game.GameDbSession.SessionUpdate(toon);
}

View File

@ -94,7 +94,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations
if (haveDrop)
{
var dropRates = World.Game.IsHardcore ? LootManager.GetSeasonalDropRates((int)Quality, Program.MaxLevel) : LootManager.GetDropRates((int)Quality, Program.MaxLevel);
var dropRates = World.Game.IsHardcore ? LootManager.GetSeasonalDropRates((int)Quality, Program.MAX_LEVEL) : LootManager.GetDropRates((int)Quality, Program.MAX_LEVEL);
foreach (var rate in dropRates)
foreach (var plr in GetPlayersInRange(30))
{

View File

@ -2,6 +2,7 @@
using DiIiS_NA.GameServer.Core.Types.TagMap;
using DiIiS_NA.GameServer.GSSystem.MapSystem;
using DiIiS_NA.GameServer.MessageSystem;
using Microsoft.Extensions.Logging;
namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations.Monsters
{
@ -27,13 +28,9 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations.Monsters
public override int Quality
{
get
{
return (int)DiIiS_NA.Core.MPQ.FileFormats.SpawnType.Boss;
}
get => (int)DiIiS_NA.Core.MPQ.FileFormats.SpawnType.Boss;
set
{
}
}

View File

@ -6,6 +6,7 @@ using GameBalance = DiIiS_NA.Core.MPQ.FileFormats.GameBalance;
using DiIiS_NA.GameServer.GSSystem.ObjectsSystem;
using DiIiS_NA.Core.Logging;
using DiIiS_NA.Core.MPQ.FileFormats;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.GameServer.GSSystem.TickerSystem;
using DiIiS_NA.GameServer.MessageSystem;
using DiIiS_NA.GameServer.Core.Types.SNO;
@ -17,12 +18,22 @@ using DiIiS_NA.GameServer.GSSystem.AISystem.Brains;
using DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations;
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
using World = DiIiS_NA.GameServer.GSSystem.MapSystem.World;
using DiIiS_NA.Core.Helpers.Math;
using DiIiS_NA.LoginServer.Toons;
using static DiIiS_NA.Core.MPQ.FileFormats.Monster;
using D3.Store;
using DiIiS_NA.GameServer.GSSystem.AISystem;
using DiIiS_NA.GameServer.GSSystem.MapSystem;
using Microsoft.EntityFrameworkCore.Metadata;
using static DiIiS_NA.Core.Logging.Logger;
using System.IO;
using System.Net.NetworkInformation;
namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
{
public class Monster : Living, IUpdateable
{
private static readonly Logger Logger = LogManager.CreateLogger(nameof(Monster));
private static readonly Logger Logger = LogManager.CreateLogger();
public override ActorType ActorType => ActorType.Monster;
public TickTimer DestroyTimer { get; }
@ -39,9 +50,9 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
public int LoreSnoId => Monster.IsValid ? ((MonsterFF)Monster.Target).SNOLore : -1;
public int MonsterType => Monster.IsValid ? (int)((MonsterFF)Monster.Target).Type : -1;
public float HpMultiplier => Monster.IsValid ? (1f + ((MonsterFF)Monster.Target).AttributeModifiers[4]) : 1f;
public int MonsterTypeValue => Monster.IsValid ? (int)((MonsterFF)Monster.Target).Type : -1;
public MonsterType MonsterType => (MonsterType)(((MonsterFF)Monster.Target)?.Type ?? MonsterType.Unknown);
public float HpMultiplier => Monster.IsValid ? (1f + ((MonsterFF)Monster.Target).AttributeModifiers[4]) : 1f;
public float DmgMultiplier => Monster.IsValid ? (1f + ((MonsterFF)Monster.Target).AttributeModifiers[55]) : 1f;
public Vector3D BasePoint { get; set; }
@ -70,7 +81,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
//WalkSpeed /= 2f;
Brain = new MonsterBrain(this);
Attributes[GameAttributes.Attacks_Per_Second] = 1.2f;
Attributes[GameAttributes.Attacks_Per_Second] = GameModsConfig.Instance.Monster.AttacksPerSecond;// 1.2f;
UpdateStats();
}
@ -80,54 +91,187 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
#if DEBUG
string monster = "monster";
if (this is Boss) monster = "boss";
Logger.MethodTrace($"Player {player.Name} targeted {monster} {GetType().Name}.");
Logger.MethodTrace($"Player {player.Name} targeted $[underline]${monster}$[/]$ {GetType().Name}.");
#endif
}
public void UpdateStats()
{
var monsterLevels = (GameBalance)DiIiS_NA.Core.MPQ.MPQStorage.Data.Assets[SNOGroup.GameBalance][19760].Data;
// TODO: Level up is getting harder from level 3+. 1 seems stable. check the difficulty.
// TODO: Level up is getting harder from level 3+. 1 seems stable. check the difficulty.
// TODO: Level up is getting harder from level 3+. 1 seems stable. check the difficulty.
var monsterLevels = (GameBalance)DiIiS_NA.Core.MPQ.MPQStorage.Data.Assets[SNOGroup.GameBalance][19760].Data;
bool fullHp = (Math.Abs(Attributes[GameAttributes.Hitpoints_Cur] - Attributes[GameAttributes.Hitpoints_Max_Total]) < Globals.FLOAT_TOLERANCE);
Attributes[GameAttributes.Level] = World.Game.MonsterLevel;
//this.Attributes[GameAttribute.Hitpoints_Max] = (int)monsterLevels.MonsterLevel[this.World.Game.MonsterLevel - 1].HPMin * (int)this.HPMultiplier * (int)this.World.Game.HPModifier;
int monsterLevel = 1;
monsterLevel = World.Game.ConnectedPlayers.Length > 1 ? World.Game.ConnectedPlayers[0].Level : World.Game.InitialMonsterLevel;
var connectedPlayers = World.Game.ConnectedPlayers.ToArray();
double maxUsersHealth = 1f;
double deltaDamageUsers = 1f;
int userLevelAverage = 1;
Attributes[GameAttributes.Hitpoints_Max] = (int)((int)monsterLevels.MonsterLevel[monsterLevel].HPMin + DiIiS_NA.Core.Helpers.Math.RandomHelper.Next(0, (int)monsterLevels.MonsterLevel[monsterLevel].HPDelta) * HpMultiplier * World.Game.HpModifier);
Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = ((int)World.Game.ConnectedPlayers.Length + 1) * 1.5f;
Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] *= GameServerConfig.Instance.RateMonsterHP;
if (World.Game.ConnectedPlayers.Length > 1)
Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative];// / 2f;
var hpMax = Attributes[GameAttributes.Hitpoints_Max];
var hpTotal = Attributes[GameAttributes.Hitpoints_Max_Total];
float damageMin = monsterLevels.MonsterLevel[World.Game.MonsterLevel].Dmg * DmgMultiplier;// * 0.5f;
float damageDelta = damageMin;
Attributes[GameAttributes.Damage_Weapon_Min, 0] = damageMin * World.Game.DmgModifier * GameServerConfig.Instance.RateMonsterDMG;
Attributes[GameAttributes.Damage_Weapon_Delta, 0] = damageDelta;
if (monsterLevel > 30)
if (connectedPlayers.Any())
{
Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative];// * 0.5f;
Attributes[GameAttributes.Damage_Weapon_Min, 0] = damageMin * World.Game.DmgModifier * GameServerConfig.Instance.RateMonsterDMG;// * 0.2f;
Attributes[GameAttributes.Damage_Weapon_Delta, 0] = damageDelta;
}
if (monsterLevel > 60)
{
Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative];// * 0.7f;
Attributes[GameAttributes.Damage_Weapon_Min, 0] = damageMin * World.Game.DmgModifier * GameServerConfig.Instance.RateMonsterDMG;// * 0.15f;
//this.Attributes[GameAttribute.Damage_Weapon_Delta, 0] = DamageDelta * 0.5f;
maxUsersHealth = connectedPlayers.Average(x => x.Attributes[GameAttributes.Hitpoints_Max]);
deltaDamageUsers = connectedPlayers.Average(x => x.Attributes[GameAttributes.Damage_Delta]);
userLevelAverage = (int)connectedPlayers.Average(x => x.Level);
Logger.MethodTrace($"$[yellow]${connectedPlayers.Length}$[/]$ $[green]$players online$[/]$: $[blue dim]${maxUsersHealth}$[/]$ $[bold]$avg. max health$[/]$ / $[blue dim italic]${deltaDamageUsers}$[/]$ $[bold]$avg. delta damage$[/]$");
}
_nativeHp = Attributes[GameAttributes.Hitpoints_Max_Total];
_nativeDmg = Attributes[GameAttributes.Damage_Weapon_Min, 0];
//if (full_hp)
Attributes[GameAttributes.Hitpoints_Cur] = Attributes[GameAttributes.Hitpoints_Max_Total];
var difficulty = World.Game.Difficulty;
var maxHP = (monsterLevels.MonsterLevel[monsterLevel].HPMin +
RandomHelper.NextFloat(0f, monsterLevels.MonsterLevel[monsterLevel].HPDelta)) *
HpMultiplier * World.Game.HpModifier;
var bonus = CalculateLevelAdjustment(LevelAdjustmentEnum.LinearScaling, difficulty, connectedPlayers);
Attributes[GameAttributes.Hitpoints_Max] = maxHP;
Attributes[GameAttributes.Hitpoints_Max_Percent_Bonus_Multiplicative] = bonus;
var baseHp = Attributes[GameAttributes.Hitpoints_Max];
var baseDamage = Attributes[GameAttributes.Damage_Weapon_Min, 0];
// Apply calculated scaling
baseHp *= bonus;
baseDamage *= bonus;
// Apply configuration modifiers
baseHp *= GameModsConfig.Instance.Monster.HealthMultiplier;
baseDamage *= GameModsConfig.Instance.Monster.DamageMultiplier;
// Assign modified values
Attributes[GameAttributes.Hitpoints_Max_Total] = baseHp;
Attributes[GameAttributes.Damage_Weapon_Min, 0] = baseDamage;
//if (full_hp)
Attributes[GameAttributes.Hitpoints_Cur] = Attributes[GameAttributes.Hitpoints_Max_Total];
Attributes.BroadcastChangedIfRevealed();
}
int _bleedFirstTick = 0;
enum LevelAdjustmentEnum { LinearScaling, DiminishedReturns, CurveScaling, LinearScalingAndDiminishedReturnsAfterThreshold }
private float CalculateLevelAdjustment(LevelAdjustmentEnum levelAdjustment, int difficulty = 0, params Player[] players)
{
var playersStats = players.Select(s =>
new
{
s.Attributes,
TotalLevel = s.Level + s.ParagonLevel * 1.05f,
Health = s.Attributes[GameAttributes.Hitpoints_Max],
Damage = s.Attributes[GameAttributes.Damage_Weapon_Min, 0],
Toughness = s.Attributes[GameAttributes.Armor_Total],
DPS = s.Attributes[GameAttributes.DPS]
}
).ToArray();
var monstersNearbyStats = players.WhereNearbyOf(World.Monsters.ToArray(), s => s.Visible && s.Alive && s.Attributes[GameAttributes.Hitpoints_Max] * 0.8 > Attributes[GameAttributes.Hitpoints_Cur], 120f, 1f).ToArray();
var monsterStats = monstersNearbyStats.Select(s =>
new
{
s.Attributes,
Health = s.Attributes[GameAttributes.Hitpoints_Max],
Damage = s.Attributes[GameAttributes.Damage_Weapon_Min, 0],
Toughness = s.Attributes[GameAttributes.Armor_Total],
DPS = s.Attributes[GameAttributes.DPS]
}
).ToArray();
// Define configuration constants
// This is the multiplier for linear scaling. It determines how much the monster's level increases for each player level.
// If you increase this value, monsters will become stronger faster as player levels increase.
const float linearMultiplierConfig = 0.025f;
// This is the multiplier for diminished returns scaling. It determines how much the monster's level increases for each player level,
// but the increase becomes smaller as player levels get higher. If you increase this value, monsters will become stronger faster at lower player levels.
const float diminishedMultiplierConfig = 0.1f;
// This is the base value for diminished returns scaling. It's the starting point for the monster's level before any scaling is applied.
// If you increase this value, monsters will start off stronger before any player level scaling is applied.
const float diminishedBaseConfig = 1.0f;
// This is the multiplier for curve scaling. It determines how much the monster's level increases for each player level,
// but the increase becomes larger as player levels get higher. If you increase this value, monsters will become stronger faster at higher player levels.
const float curveMultiplierConfig = 0.1f;
// This is the base value for curve scaling. It's the starting point for the monster's level before any scaling is applied.
// If you increase this value, monsters will start off stronger before any player level scaling is applied.
const float curveBaseConfig = 30.0f;
// This is the exponent for curve scaling. It determines the shape of the curve for how much the monster's level increases for each player level.
// If you increase this value, the curve will be steeper, meaning monsters will become much stronger at higher player levels.
const float curveExponentConfig = 0.1f;
// This is the multiplier for linear scaling after a certain threshold. It determines how much the monster's level increases for each player level
// after the player level has reached a certain threshold. If you increase this value, monsters will become stronger faster after player levels reach the threshold.
const float linearMultiplierThresholdConfig = 0.005f;
// This is the multiplier for log scaling. It determines how much the monster's level increases for each player level,
// but the increase becomes smaller as player levels get higher. If you increase this value, monsters will become stronger faster at lower player levels.
const float logMultiplierConfig = 0.1f;
// This is the threshold for linear scaling. It determines the player level at which linear scaling starts to apply.
// If you increase this value, linear scaling will start to apply at higher player levels.
const float thresholdConfig = 40.0f;
// This is the ratio for DPS (Damage Per Second) scaling. It determines how much the monster's level increases for each unit of player DPS.
// If you increase this value, monsters will become stronger faster as player DPS increases.
const float dpsRatioConfig = 1.2f;
// This is the ratio for toughness scaling. It determines how much the monster's level increases for each unit of player toughness.
// If you increase this value, monsters will become stronger faster as player toughness increases.
const float toughnessRatioConfig = 0.1f;
// Define variables for average user and monster stats
// float avgUserLevel = playersStats.Average(s => s.TotalLevel);
// float avgUserDPS = playersStats.Average(s => s.DPS);
// float avgUserToughness = playersStats.Average(s => s.Toughness);
//float avgMonsterDPS = playersStats.Average(s => s.DPS);
// float avgMonsterToughness = monsterStats.Average(s => s.Toughness);
//var tierMultiplier = GetMonsterTierMultiplier();
float avgUserLevel = 1f, avgUserDPS = 1f, avgUserToughness = 1f, avgMonsterDPS = 1f, avgMonsterToughness = 1f, tierMultiplier = 1f;
if (playersStats.Any())
{
avgUserLevel = playersStats.Average(s => s.TotalLevel);
avgUserDPS = playersStats.Average(s => s.DPS);
avgUserToughness = playersStats.Average(s => s.Toughness);
}
if (monsterStats.Any())
{
avgMonsterDPS = playersStats.Average(s => s.DPS);
avgMonsterToughness = monsterStats.Average(s => s.Toughness);
tierMultiplier = GetMonsterTierMultiplier();
}
float LevelScaling() => 1.0f + 0.1f * MathF.Log10(avgUserLevel + 1) * tierMultiplier;
float DiminishedReturns() => diminishedBaseConfig + diminishedMultiplierConfig * avgUserLevel;
float CurveScaling() => curveBaseConfig * MathF.Pow(avgUserLevel, curveExponentConfig) * curveMultiplierConfig;
float LinearScalingAndDiminishedReturnsAfterThreshold() => MathF.Max(1.0f, MathF.Min(1.5f, logMultiplierConfig * MathF.Log10(avgUserLevel + 1) + (avgUserLevel - thresholdConfig) * linearMultiplierThresholdConfig) * tierMultiplier);
return levelAdjustment switch
{
LevelAdjustmentEnum.LinearScaling => LevelScaling(),
LevelAdjustmentEnum.DiminishedReturns => DiminishedReturns(),
LevelAdjustmentEnum.CurveScaling => CurveScaling(),
LevelAdjustmentEnum.LinearScalingAndDiminishedReturnsAfterThreshold => LinearScalingAndDiminishedReturnsAfterThreshold(),
_ => LinearScalingAndDiminishedReturnsAfterThreshold()
};
}
private float GetMonsterTierMultiplier()
{
return MonsterType switch
{
MonsterType.Beast => 1.1f,
MonsterType.Demon => 1.15f,
MonsterType.Human => 1.25f,
MonsterType.Undead => 1.4f, // Steeper jump here
_ => 1.0f,
};
}
int _bleedFirstTick = 0;
int _caltropsFirstTick = 0;
public void Update(int tickCounter)
@ -187,14 +331,12 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
lock (_adjustLock)
{
int count = player.World.Game.Players.Count;
if (count > 0 && _adjustedPlayers != count)
{
Attributes[GameAttributes.Damage_Weapon_Min, 0] = _nativeDmg * (1f + (0.05f * (count - 1) * player.World.Game.Difficulty));
Attributes[GameAttributes.Hitpoints_Max] = _nativeHp * (1f + ((0.75f + (0.1f * player.World.Game.Difficulty)) * (count - 1)));
Attributes[GameAttributes.Hitpoints_Cur] = Attributes[GameAttributes.Hitpoints_Max_Total];
Attributes.BroadcastChangedIfRevealed();
_adjustedPlayers = count;
}
if (count <= 0 || _adjustedPlayers == count) return true;
Attributes[GameAttributes.Damage_Weapon_Min, 0] = _nativeDmg * (1f + (0.05f * (count - 1) * player.World.Game.Difficulty));
Attributes[GameAttributes.Hitpoints_Max] = _nativeHp * (1f + ((0.75f + (0.1f * player.World.Game.Difficulty)) * (count - 1)));
Attributes[GameAttributes.Hitpoints_Cur] = Attributes[GameAttributes.Hitpoints_Max_Total];
Attributes.BroadcastChangedIfRevealed();
_adjustedPlayers = count;
}
return true;

View File

@ -0,0 +1,11 @@
namespace DiIiS_NA.GameServer.GSSystem.GameSystem;
public enum ActEnum
{
Act1 = 0,
Act2 = 100,
Act3 = 200,
Act4 = 300,
Act5 = 400,
OpenWorld = 3000
}

View File

@ -26,7 +26,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
private bool SenderMessageReceived(byte[] data)
{
string msg = "";
if (data != null && data.Length > 0) msg = Encoding.UTF8.GetString(data);
if (data is { Length: > 0 }) msg = Encoding.UTF8.GetString(data);
Logger.Debug("Message from Battle.net: {0}", msg);
var message = msg.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
@ -74,8 +74,11 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
string backEndIp = GameServerConfig.Instance.BindIP;
int backEndPort = GameServerConfig.Instance.Port;
bool pvp = false;
if (!pvp)
Logger.Info("We are here");
if (!pvp){
Logger.Info("Ip: {0}|{1}", backEndIp, backEndPort);
RegisterGameServer(backEndIp, backEndPort);
}
else
RegisterPvPGameServer(backEndIp, backEndPort);
return true;

View File

@ -31,27 +31,18 @@ using DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations.Hirelings;
using DiIiS_NA.GameServer.GSSystem.GeneratorsSystem;
using DiIiS_NA.GameServer.GSSystem.AISystem.Brains;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using DiIiS_NA.Core.MPQ.FileFormats;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
using DiIiS_NA.D3_GameServer.GSSystem.GameSystem;
using Actor = DiIiS_NA.GameServer.GSSystem.ActorSystem.Actor;
using Monster = DiIiS_NA.GameServer.GSSystem.ActorSystem.Monster;
using Scene = DiIiS_NA.GameServer.GSSystem.MapSystem.Scene;
using World = DiIiS_NA.GameServer.GSSystem.MapSystem.World;
using System.Runtime.CompilerServices;
namespace DiIiS_NA.GameServer.GSSystem.GameSystem
{
public enum ActEnum
{
Act1 = 0,
Act2 = 100,
Act3 = 200,
Act4 = 300,
Act5 = 400,
OpenWorld = 3000
}
public class Game : IMessageConsumer
{
private static readonly Logger Logger = LogManager.CreateLogger();
@ -66,6 +57,8 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
/// </summary>
public ConcurrentDictionary<GameClient, Player> Players { get; private set; }
public Player FirstPlayer() => Players.Values.First();
public ImmutableArray<Player> ConnectedPlayers => Players
.Where(s => s.Value != null && s.Key.Connection.IsOpen() && !s.Key.IsLoggingOut)
.Select(s => s.Value).ToImmutableArray();
@ -145,8 +138,8 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
public int AcceptedPlayers;
};
public readonly Dictionary<WorldSno, List<Action>> OnLoadWorldActions = new();
public readonly Dictionary<int, List<Action>> OnLoadSceneActions = new();
public readonly Dictionary<WorldSno, List<System.Action>> OnLoadWorldActions = new();
public readonly Dictionary<int, List<System.Action>> OnLoadSceneActions = new();
public BossEncounter CurrentEncounter = new() { SnoId = -1, Activated = false, AcceptedPlayers = 0 };
@ -190,9 +183,10 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
/// Current quest SNOid.
/// </summary>
public int CurrentQuest = -1;
public int CurrentSideQuest = -1;
public bool IsCurrentOpenWorld => CurrentQuest == 312429;
/// <summary>
/// Current quest step SNOid.
/// </summary>
@ -667,7 +661,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
/// <param name="joinedPlayer">The new player.</param>
public void Enter(Player joinedPlayer)
{
if (IsHardcore && !joinedPlayer.Toon.DBToon.isHardcore)
if (IsHardcore && !joinedPlayer.Toon.DbToon.isHardcore)
{
return;
}
@ -1276,18 +1270,28 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
public void SetDifficulty(int diff)
{
Difficulty = diff;
if (Difficulty < 0) Difficulty = 0;
if (Difficulty > 19) Difficulty = 19;
Difficulty = Math.Clamp(diff, 0, 19);
diff++;
if (diff > 0)
{
var handicapLevels = (GameBalance)MPQStorage.Data.Assets[SNOGroup.GameBalance][256027].Data;
HpModifier = handicapLevels.HandicapLevelTables[diff].HPMod;
DmgModifier = handicapLevels.HandicapLevelTables[diff].DmgMod;
XpModifier = (1f + handicapLevels.HandicapLevelTables[diff].XPMod);
GoldModifier = (1f + handicapLevels.HandicapLevelTables[diff].GoldMod);
HpModifier = handicapLevels.HandicapLevelTables[diff].HPMod * GameModsConfig.Instance.Rate.HealthByDifficulty[Difficulty]
* GameModsConfig.Instance.Monster.HealthMultiplier;
DmgModifier = handicapLevels.HandicapLevelTables[diff].DmgMod
* GameModsConfig.Instance.Rate.GetDamageByDifficulty(diff)
* GameModsConfig.Instance.Monster.DamageMultiplier;
XpModifier = (1f + handicapLevels.HandicapLevelTables[diff].XPMod) * GameModsConfig.Instance.Rate.Experience;
GoldModifier = (1f + handicapLevels.HandicapLevelTables[diff].GoldMod * GameModsConfig.Instance.Rate.Gold);
}
else
{
HpModifier = GameModsConfig.Instance.Rate.HealthByDifficulty[diff] * GameModsConfig.Instance.Monster.HealthMultiplier;
DmgModifier = GameModsConfig.Instance.Rate.GetDamageByDifficulty(diff) * GameModsConfig.Instance.Monster.DamageMultiplier;
XpModifier = 1f + GameModsConfig.Instance.Rate.Experience;
GoldModifier = (1f * GameModsConfig.Instance.Rate.Gold);
}
Logger.Info($"$[italic]$Updated Game #$[underline]${GameId}$[/]$ difficulty to {diff}.$[/]$");
foreach (var wld in _worlds)
foreach (var monster in wld.Value.Monsters)
@ -1336,13 +1340,13 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
target.InGameClient.SendMessage(new NewPlayerMessage
{
PlayerIndex = joinedPlayer.PlayerIndex,
NewToonId = (long)joinedPlayer.Toon.D3EntityID.IdLow,
NewToonId = (long)joinedPlayer.Toon.D3EntityId.IdLow,
GameAccountId = new GameAccountHandle()
{ ID = (uint)joinedPlayer.Toon.GameAccount.BnetEntityId.Low, Program = 0x00004433, Region = 1 },
ToonName = joinedPlayer.Toon.Name,
Team = 0x00000002,
Class = joinedPlayer.ClassSno,
snoActorPortrait = joinedPlayer.Toon.DBToon.Cosmetic4,
snoActorPortrait = joinedPlayer.Toon.DbToon.Cosmetic4,
Level = joinedPlayer.Toon.Level,
AltLevel = (ushort)joinedPlayer.Toon.ParagonLevel,
HighestHeroSoloRiftLevel = 0,
@ -1570,9 +1574,8 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
//handling quest triggers
if (QuestProgress.QuestTriggers.ContainsKey(levelArea)) //EnterLevelArea
if (QuestProgress.QuestTriggers.TryGetValue(levelArea, out var trigger)) //EnterLevelArea
{
var trigger = QuestProgress.QuestTriggers[levelArea];
if (trigger.TriggerType == QuestStepObjectiveType.EnterLevelArea)
{
try
@ -1645,7 +1648,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
//Берем каина
var firstPoint = new Vector3D(120.92718f, 121.26151f, 0.099973306f);
var secondPoint = new Vector3D(120.73298f, 160.61829f, 0.31863004f);
var sceletonPoint = new Vector3D(120.11514f, 140.77332f, 0.31863004f);
var sketonPosition = new Vector3D(120.11514f, 140.77332f, 0.31863004f);
var firstfacingAngle =
ActorSystem.Movement.MovementHelpers.GetFacingAngle(cainRun, firstPoint);
@ -1666,9 +1669,9 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
{
foreach (var skeleton in skeletons)
{
skeleton.Move(sceletonPoint,
skeleton.Move(sketonPosition,
ActorSystem.Movement.MovementHelpers.GetFacingAngle(skeleton,
sceletonPoint));
sketonPosition));
}
cainRun.Move(secondPoint, secondfacingAngle);

View File

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NHibernate.Util;
namespace DiIiS_NA.GameServer.GSSystem.GameSystem
{
@ -20,8 +21,8 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
public static Game CreateGame(int gameId, int initialLevel)
{
if (Games.ContainsKey(gameId))
return Games[gameId];
if (Games.TryGetValue(gameId, out var createdGame))
return createdGame;
var game = new Game(gameId, initialLevel);
Games.Add(gameId, game);
@ -35,84 +36,98 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
public static int GetIdByGame(Game game)
{
return !Games.ContainsValue(game) ? -1 : Games.Keys.Where(g => Games[g] == game).First();
return !Games.ContainsValue(game) ? -1 : Games.Keys.First(g => Games[g] == game);
}
public static void RemovePlayerFromGame(GameClient gameClient)
{
try
{
if (gameClient == null)
{
Logger.Error("RemovePlayerFromGame() gameClient is null!");
return;
}
try
{
if (gameClient == null)
{
Logger.Error("RemovePlayerFromGame() gameClient is null!");
return;
}
foreach (var player in gameClient.Game.Players.Keys)
{
if (player == gameClient)
player.SendMessage(new QuitGameMessage()
{
PlayerIndex = gameClient.Player.PlayerIndex,
});
else
player.SendMessage(new PlayerIndexMessage(Opcodes.PlayerLeaveGameMessage) //PlayerLeaveGameMessage
{
PlayerIndex = gameClient.Player.PlayerIndex,
});
}
foreach (var player in gameClient.Game.Players.Keys)
{
if (player == gameClient)
player.SendMessage(new QuitGameMessage()
{
PlayerIndex = gameClient.Player.PlayerIndex,
});
else
player.SendMessage(
new PlayerIndexMessage(Opcodes.PlayerLeaveGameMessage) //PlayerLeaveGameMessage
{
PlayerIndex = gameClient.Player.PlayerIndex,
});
}
if (gameClient.Game != null)
{
var gameId = gameClient.Game.GameId;
if (!Games.ContainsKey(gameId)) return;
if (gameClient.Game != null)
{
var gameId = gameClient.Game.GameId;
if (!Games.ContainsKey(gameId)) return;
var game = Games[gameId];
if (!game.Players.ContainsKey(gameClient)) return;
var game = Games[gameId];
if (!game.Players.ContainsKey(gameClient)) return;
Player p = null;
if (!game.Players.TryRemove(gameClient, out p))
{
Logger.Error("Can't remove player ({0}) from game with id: {1}", gameClient.Player.Toon.Name, gameId);
return;
}
Player p = null;
if (!game.Players.TryRemove(gameClient, out p))
{
Logger.Error("Can't remove player ({0}) from game with id: {1}", gameClient.Player.Toon.Name,
gameId);
return;
}
if (p != null)
{
if (p != null)
{
//TODO: Move this inside player OnLeave event
var toon = p.Toon;
toon.TimePlayed += (int)(DateTimeExtensions.ToUnixTime(DateTime.UtcNow) - toon.LoginTime);
toon.ExperienceNext = p.ExperienceNext;
//TODO: Move this inside player OnLeave event
var toon = p.Toon;
toon.TimePlayed += (int)(DateTime.UtcNow.ToUnixTime() - toon.LoginTime);
toon.ExperienceNext = p.ExperienceNext;
ClientSystem.GameServer.GSBackend.PlayerLeft(gameId);
ClientSystem.GameServer.GSBackend.PlayerLeft(gameId);
if (p.InGameClient != null)
{
var minions = p.Followers.Keys.ToList();
foreach (var minion in minions)
p.DestroyFollowerById(minion);
p.World.Leave(p);
}
if (p.InGameClient != null)
{
var minions = p.Followers.Keys.ToList();
foreach (var minion in minions)
p.DestroyFollowerById(minion);
p.World.Leave(p);
}
if (gameClient.BnetClient != null)
{
gameClient.BnetClient.Account.GameAccount.ScreenStatus = D3.PartyMessage.ScreenStatus.CreateBuilder().SetScreen(1).SetStatus(0).Build();
gameClient.BnetClient.Account.GameAccount.NotifyUpdate();
if (gameClient.BnetClient != null)
{
gameClient.BnetClient.Account.GameAccount.ScreenStatus = D3.PartyMessage.ScreenStatus
.CreateBuilder().SetScreen(1).SetStatus(0).Build();
gameClient.BnetClient.Account.GameAccount.NotifyUpdate();
}
else
{
try { ClientSystem.GameServer.GSBackend.UpdateClient(toon.GameAccount.PersistentID, toon.Level, 1); } catch { Logger.Warn("Exception on RemovePlayerFromGame()"); }
}
}
}
else
{
Logger.Error("RemovePlayerFromGame() gameClient.Game is null!");
}
}
catch { }
}
else
{
try
{
ClientSystem.GameServer.GSBackend.UpdateClient(toon.GameAccount.PersistentID,
toon.Level, 1);
}
catch
{
Logger.Warn("Exception on RemovePlayerFromGame()");
}
}
}
}
else
{
Logger.Error("RemovePlayerFromGame() gameClient.Game is null!");
}
}
catch (Exception ex)
{
Logger.ErrorException(ex, nameof(RemovePlayerFromGame));
}
}
}
}

View File

@ -6,11 +6,12 @@ using DiIiS_NA.Core.Logging;
namespace DiIiS_NA.GameServer.GSSystem.GameSystem
{
public static class GameUpdateManager
{
private static readonly Logger Logger = LogManager.CreateLogger("ThreadSystem");
[Obsolete("This class is obsolete and will be removed in the future.")]
public class GameUpdateManager
{
private static readonly Logger Logger = LogManager.CreateLogger<GameUpdateManager>();
private static List<GameUpdateThread> UpdateWorkers = new List<GameUpdateThread>();
private static readonly List<GameUpdateThread> _updateWorkers = new();
static GameUpdateManager()
{
@ -22,17 +23,17 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
for (int coreId = 0; coreId < CPUCount; coreId++)
{
var thread = new GameUpdateThread();
thread.CPUAffinity = (1UL << coreId);
UpdateWorkers.Add(thread);
var loopThread = new Thread(thread.Run) { Name = "UpdateWorkerThread", IsBackground = true }; ; // create the game update thread.
//thread.CPUAffinity = (1UL << coreId);
_updateWorkers.Add(thread);
var loopThread = new Thread(thread.Run) { Name = "UpdateWorkerThread", IsBackground = true };
loopThread.Start();
}
Logger.Info("Запущено {0} потоков", CPUCount);
Logger.Info("Started {0} threads", CPUCount);
}
public static GameUpdateThread FindWorker()
{
return UpdateWorkers.OrderBy(t => t.Games.Count).First();
return _updateWorkers.OrderBy(t => t.Games.Count).First();
}
}
}

View File

@ -37,7 +37,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
public void Run()
{
List<Game> InactiveGames = new List<Game>();
List<Game> inactiveGames = new List<Game>();
int missedTicks = 0;
Thread.BeginThreadAffinity();
@ -51,15 +51,15 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
while (true)
{
Stopwatch _tickWatch = new Stopwatch();
_tickWatch.Restart();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Restart();
lock (_lock)
{
foreach (var game in Games)
{
if (!game.Working)
InactiveGames.Add(game);
inactiveGames.Add(game);
else
{
if (!game.UpdateInProgress)
@ -67,11 +67,15 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
game.UpdateInProgress = true;
Task.Run(() =>
{
try
{
game.Update();
}
catch { }
try
{
game.Update();
}
catch (Exception ex)
{
Logger.ErrorException(ex, "Error in Game.Update()");
}
game.MissedTicks = 0;
game.UpdateInProgress = false;
});
@ -79,25 +83,33 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
else
{
game.MissedTicks += 6;
if (game.MissedTicks > 60)
{
Logger.Warn("Game.Update() is running too slow. GameId: {0}", game.GameId);
game.MissedTicks = 0;
}
}
}
}
foreach (var game in InactiveGames)
Games.Remove(game);
foreach (var game in inactiveGames)
{
game.Working = false;
Games.Remove(game);
}
InactiveGames.Clear();
inactiveGames.Clear();
}
_tickWatch.Stop();
stopwatch.Stop();
var compensation = (int)(100 - _tickWatch.ElapsedMilliseconds); // the compensation value we need to sleep in order to get consistent 100 ms Game.Update().
var compensation = (int)(100 - stopwatch.ElapsedMilliseconds); // the compensation value we need to sleep in order to get consistent 100 ms Game.Update().
if (_tickWatch.ElapsedMilliseconds > 100)
if (stopwatch.ElapsedMilliseconds > 100)
{
Logger.Trace("Game.Update() took [{0}ms] more than Game.UpdateFrequency [{1}ms].", _tickWatch.ElapsedMilliseconds, 100);
compensation = (int)(100 - (_tickWatch.ElapsedMilliseconds % 100));
missedTicks = 6 * (int)(_tickWatch.ElapsedMilliseconds / 100);
Logger.Trace("Game.Update() took [{0}ms] more than Game.UpdateFrequency [{1}ms].", stopwatch.ElapsedMilliseconds, 100);
compensation = (int)(100 - (stopwatch.ElapsedMilliseconds % 100));
missedTicks = 6 * (int)(stopwatch.ElapsedMilliseconds / 100);
Thread.Sleep(Math.Max(0, compensation)); // sleep until next Update().
}
else

View File

@ -21,6 +21,7 @@ using DiIiS_NA.GameServer.MessageSystem;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Map;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Quest;
using DiIiS_NA.GameServer.MessageSystem.Message.Fields;
using Spectre.Console;
using Monster = DiIiS_NA.GameServer.GSSystem.ActorSystem.Monster;
namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
@ -29,11 +30,6 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
{
private static readonly Logger Logger = new(nameof(QuestManager));
/// <summary>
/// Accessor for quests
/// </summary>
/// <param name="snoQuest">snoId of the quest to retrieve</param>
/// <returns></returns>
public readonly Dictionary<int, QuestRegistry.Quest> Quests = new();
public readonly Dictionary<int, QuestRegistry.Quest> SideQuests = new();
@ -164,12 +160,30 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
Bounties.AddRange(actToKillUniqueBounties[BountyData.ActT.A5].Take(4));
}
private readonly struct Rewards
{
public int Experience { get; }
public int Gold { get; }
public Rewards(int experience, int gold)
{
Experience = experience;
Gold = gold;
}
public Rewards(float experience, float gold) : this((int) Math.Floor(experience), (int) Math.Floor(gold)) {}
}
private Rewards GetCurrentQuestRewards() =>
new Rewards(Quests[Game.CurrentQuest].RewardXp, Quests[Game.CurrentQuest].RewardGold);
/// <summary>
/// Advances a quest by a step
/// </summary>
/// <param name="snoQuest">snoID of the quest to advance</param>
public void Advance()
{
int oldQuest = Game.CurrentQuest;
int oldStep = Game.CurrentStep;
Quests[Game.CurrentQuest].Steps[Game.CurrentStep].Completed = true;
Game.CurrentStep = Quests[Game.CurrentQuest].Steps[Game.CurrentStep].NextStep;
Game.QuestProgress.QuestTriggers.Clear();
@ -185,6 +199,13 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
if (Quests[Game.CurrentQuest].Steps[Game.CurrentStep].NextStep != -1)
{
Logger.QuestInfo(
$"{Emoji.Known.RightArrow} Step Advance ".StyleAnsi("deeppink4") +
$"Game #{Game.GameId.StyleAnsi("underline")} " +
$"from quest {oldQuest}/" +
$"step {oldStep.StyleAnsi("deeppink4")}" +
$"to quest {Game.CurrentQuest}'s " +
$"step {Game.CurrentStep.StyleAnsi("deeppink4")}");
}
else
{
@ -192,23 +213,25 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
if (!Game.Empty)
{
SaveQuestProgress(true);
Logger.Trace(
$"$[white]$(Advance)$[/]$ Game {Game.GameId} Advanced to quest $[underline white]${Game.CurrentQuest}$[/]$, completed $[underline white]${Quests[Game.CurrentQuest].Completed}$[/]$");
Logger.QuestInfo(
$"{Emoji.Known.NextTrackButton} Quest Advance ".StyleAnsi("white") +
$"Game #{Game.GameId.StyleAnsi("underline")} " +
$"from quest {oldQuest.StyleAnsi("turquoise2")}/" +
$"step {oldStep.StyleAnsi("deeppink4")}" +
$"to quest {Game.CurrentQuest.StyleAnsi("turquoise2")}/" +
$"step {Game.CurrentStep.StyleAnsi("deeppink4")}");
Game.BroadcastPlayers((client, player) =>
{
if (Game.CurrentQuest == 312429) return; // open world quest
if (Game.IsCurrentOpenWorld) return; // open world quest
int xpReward = (int)(Quests[Game.CurrentQuest].RewardXp *
Game.XpModifier);
int goldReward = (int)(Quests[Game.CurrentQuest].RewardGold *
Game.GoldModifier);
var rewards = GetCurrentQuestRewards();
player.InGameClient.SendMessage(new QuestStepCompleteMessage()
{
QuestStepComplete = QuestStepComplete.CreateBuilder()
.SetReward(QuestReward.CreateBuilder()
.SetGoldGranted(goldReward)
.SetXpGranted((ulong)xpReward)
.SetGoldGranted(rewards.Gold)
.SetXpGranted((ulong)rewards.Experience)
.SetSnoQuest(Game.CurrentQuest)
)
.SetIsQuestComplete(true)
@ -224,7 +247,7 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
WorldID = player.World.DynamicID(player),
},
Amount = xpReward,
Amount = rewards.Experience,
Type = GameServer.MessageSystem.Message.Definitions.Base
.FloatingAmountMessage.FloatType.Experience,
});
@ -238,13 +261,13 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
WorldID = player.World.DynamicID(player),
},
Amount = goldReward,
Amount = rewards.Gold,
Type = GameServer.MessageSystem.Message.Definitions.Base
.FloatingAmountMessage.FloatType.Gold,
});
player.UpdateExp(xpReward);
player.Inventory.AddGoldAmount(goldReward);
player.AddAchievementCounter(74987243307173, (uint)goldReward);
player.UpdateExp(rewards.Experience);
player.Inventory.AddGoldAmount(rewards.Gold);
player.AddAchievementCounter(74987243307173, (uint)rewards.Gold);
player.CheckQuestCriteria(Game.CurrentQuest);
});
}
@ -270,7 +293,7 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
if (!Game.Empty)
{
RevealQuestProgress();
if ((Game.CurrentActEnum != ActEnum.OpenWorld && GameServerConfig.Instance.AutoSaveQuests) ||
if ((Game.CurrentActEnum != ActEnum.OpenWorld && GameModsConfig.Instance.Quest.AutoSave) ||
Quests[Game.CurrentQuest].Steps[Game.CurrentStep].Saveable)
SaveQuestProgress(false);
}
@ -337,7 +360,7 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
{
player.World.SpawnRandomEquip(player, player, LootManager.Epic, player.Attributes[GameAttributes.Level]);
}
var toon = player.Toon.DBToon;
var toon = player.Toon.DbToon;
toon.EventsCompleted++;
Game.GameDbSession.SessionUpdate(toon);
player.CheckQuestCriteria(Game.CurrentSideQuest);
@ -827,7 +850,7 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.GameSystem
{
var questHistory = new DBQuestHistory
{
DBToon = player.Toon.DBToon,
DBToon = player.Toon.DbToon,
QuestId = Game.CurrentQuest,
QuestStep = Game.CurrentStep
};

View File

@ -2295,7 +2295,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GeneratorsSystem
}
}
if (gizmoLocations.Count > 0 && world.Game.MonsterLevel >= Program.MaxLevel && FastRandom.Instance.Next(100) < 30)
if (gizmoLocations.Count > 0 && world.Game.MonsterLevel >= Program.MAX_LEVEL && FastRandom.Instance.Next(100) < 30)
{
var handleChest = new SNOHandle(96993); //leg chest
if (handleChest == null) continue;

View File

@ -23,42 +23,25 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
foreach (var asset in MPQStorage.Data.Assets[SNOGroup.GameBalance].Values)
{
GameBalance data = asset.Data as GameBalance;
if (data != null && data.Type == BalanceType.AffixList)
if (data is not { Type: BalanceType.AffixList }) continue;
foreach (AffixTable affixDefinition in data.Affixes.Where(affixDefinition => affixDefinition.Hash == AffixGbid))
{
foreach (var affixDefinition in data.Affixes)
{
if (affixDefinition.Hash == AffixGbid) return affixDefinition;
}
return affixDefinition;
}
}
return null;
}
}
public int Price
{
get
{
return (Definition == null ? 0 : Definition.Cost);
}
}
public int Price => (Definition == null ? 0 : Definition.Cost);
public int ItemLevel
{
get
{
return (Definition == null ? 0 : Definition.AffixLevel);
}
}
public int ItemLevel => (Definition == null ? 0 : Definition.AffixLevel);
public float Score = 0f;
public int Rating
{
get
{
return (int)(Price * (1 + Score));
}
get => (int)(Price * (1 + Score));
set { }
}
@ -69,7 +52,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
public override String ToString()
{
return String.Format("{0}", AffixGbid);
return AffixGbid.ToString();
}
public static Affix Parse(String affixString)

View File

@ -115,30 +115,30 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
if (Item.IsAccessory(item.ItemType) && affixesCount <= 1) affixesCount = 2;
bool IsUnique = item.ItemDefinition.Name.Contains("Unique_");
bool isUnique = item.ItemDefinition.Name.Contains("Unique_");
if (IsUnique && !isCrafting) affixesCount = item.ItemDefinition.BonusAffixes + item.ItemDefinition.BonusMajorAffixes + item.ItemDefinition.BonusMinorAffixes;
if (isUnique && !isCrafting) affixesCount = item.ItemDefinition.BonusAffixes + item.ItemDefinition.BonusMajorAffixes + item.ItemDefinition.BonusMinorAffixes;
if (item.ItemDefinition.Name.ToLower().Contains("p71_ethereal"))
{
affixesCount = 8;
IsUnique = true;
isUnique = true;
}
if (item.GBHandle.GBID == -4139386) affixesCount = 6; //referral ring
if (IsUnique)
if (isUnique)
affixesCount += 3;
Class ItemPlayerClass = Class.None;
if (item.ItemType.Usable.HasFlag(ItemFlags.Barbarian)) ItemPlayerClass = Class.Barbarian;
if (item.ItemType.Usable.HasFlag(ItemFlags.Crusader)) ItemPlayerClass = Class.Crusader;
if (item.ItemType.Usable.HasFlag(ItemFlags.Necromancer)) ItemPlayerClass = Class.Necromancer;
if (item.ItemType.Usable.HasFlag(ItemFlags.DemonHunter)) ItemPlayerClass = Class.DemonHunter;
if (item.ItemType.Usable.HasFlag(ItemFlags.Wizard)) ItemPlayerClass = Class.Wizard;
if (item.ItemType.Usable.HasFlag(ItemFlags.WitchDoctor)) ItemPlayerClass = Class.Witchdoctor;
if (item.ItemType.Usable.HasFlag(ItemFlags.Monk)) ItemPlayerClass = Class.Monk;
Class itemPlayerClass = Class.None;
if (item.ItemType.Usable.HasFlag(ItemFlags.Barbarian)) itemPlayerClass = Class.Barbarian;
if (item.ItemType.Usable.HasFlag(ItemFlags.Crusader)) itemPlayerClass = Class.Crusader;
if (item.ItemType.Usable.HasFlag(ItemFlags.Necromancer)) itemPlayerClass = Class.Necromancer;
if (item.ItemType.Usable.HasFlag(ItemFlags.DemonHunter)) itemPlayerClass = Class.DemonHunter;
if (item.ItemType.Usable.HasFlag(ItemFlags.Wizard)) itemPlayerClass = Class.Wizard;
if (item.ItemType.Usable.HasFlag(ItemFlags.WitchDoctor)) itemPlayerClass = Class.Witchdoctor;
if (item.ItemType.Usable.HasFlag(ItemFlags.Monk)) itemPlayerClass = Class.Monk;
List<int> itemTypes = ItemGroup.HierarchyToHashList(item.ItemType);
@ -162,14 +162,14 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
IEnumerable<AffixTable> filteredList = null;
filteredList = AllAffix.Where(a =>
(a.PlayerClass == ItemPlayerClass || a.PlayerClass == Class.None) &&//(a.PlayerClass == ItemPlayerClass || a.PlayerClass == Class.None) &&
(a.PlayerClass == itemPlayerClass || a.PlayerClass == Class.None) &&//(a.PlayerClass == ItemPlayerClass || a.PlayerClass == Class.None) &&
itemTypes.ContainsAtLeastOne(a.ItemGroup) &&
(a.AffixLevelMax >= levelToFind) &&
(a.OverrideLevelReq <= item.ItemDefinition.RequiredLevel)
//!a.Name.Contains("1xx_Inferior")
);
if (IsUnique)
if (isUnique)
{
var restrictedFamily = item.ItemDefinition.LegendaryAffixFamily.Where(af => af != -1).ToHashSet();
filteredList = filteredList
@ -298,7 +298,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
//Logger.Debug("Affix " + def.Hash + ", final score is" + affix.Score);
item.AffixList.Add(affix);
if (affixesCount > 0 && !IsUnique && !item.ItemDefinition.Name.Contains("StaffOfCow"))
if (affixesCount > 0 && !isUnique && !item.ItemDefinition.Name.Contains("StaffOfCow"))
{
item.RareItemName = GenerateItemName();
}
@ -306,7 +306,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
n++;
}
if (IsUnique)
if (isUnique)
{
for (int i = 0; i < 6; i++)
{

View File

@ -46,7 +46,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
set
{
Attributes[GameAttributes.Unidentified] = value;
if (DBInventory is {} dbInventory) dbInventory.Unidentified = value;
if (DBInventory is { } dbInventory) dbInventory.Unidentified = value;
}
}
@ -832,14 +832,88 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
public override void OnTargeted(Player player, TargetMessage message)
{
player.Inventory.RefreshInventoryToClient();
var playerAcc = player.InGameClient.BnetClient.Account.GameAccount;
switch (SNO)
{
case ActorSno._tieredlootrunkey_0:
case ActorSno._tieredlootrunkey_0: //Greater Rift Key
playerAcc.BigPortalKey++;
Destroy();
break;
case ActorSno._crafting_assortedparts_05: //Reusable Parts
playerAcc.CraftItem1++;
Destroy();
break;
case ActorSno._crafting_magic_05: //Arcanes Dust
playerAcc.CraftItem2++;
Destroy();
break;
case ActorSno._crafting_rare_05: //Veiled Crystal
playerAcc.CraftItem3++;
Destroy();
break;
case ActorSno._crafting_looted_reagent_05: //Death's Breath
playerAcc.CraftItem4++;
Destroy();
break;
case ActorSno._crafting_legendary_05: //Forgotten Soul
playerAcc.CraftItem5++;
Destroy();
break;
case ActorSno._craftingreagent_legendary_set_borns_x1: //Khanduran Rune Bounty itens Act I.
playerAcc.HoradricA1Res++;
Destroy();
break;
case ActorSno._craftingreagent_legendary_set_cains_x1: //Caldeum Nightshade Bounty itens Act II.
playerAcc.HoradricA2Res++;
Destroy();
break;
case ActorSno._craftingreagent_legendary_set_demon_x1: //Arreat War Tapestry Bounty itens Act III.
playerAcc.HoradricA3Res++;
Destroy();
break;
case ActorSno._craftingreagent_legendary_set_hallowed_x1: //Corrupted Angel Flesh Bounty itens Act IV.
playerAcc.HoradricA4Res++;
Destroy();
break;
case ActorSno._craftingreagent_legendary_set_captaincrimsons_x1: //Westmarch Holy Water Bounty itens Act V.
playerAcc.HoradricA5Res++;
Destroy();
break;
case ActorSno._demonorgan_skeletonking_x1: //Leorik Regret.
playerAcc.LeorikKey++;
Destroy();
break;
case ActorSno._demonorgan_ghom_x1: //Vial of Putridness.
playerAcc.VialofPutridness++;
Destroy();
break;
case ActorSno._demonorgan_siegebreaker_x1: //Idol of Terror.
playerAcc.IdolofTerror++;
Destroy();
break;
case ActorSno._demonorgan_diablo_x1: //Heart of Fright.
playerAcc.HeartofFright++;
Destroy();
break;
//case ActorSno._currency_platinum_flippy: //Platinum coin
// playerAcc.Platinum++;
// Destroy();
// break;
default:
player.Inventory.PickUp(this);
break;
@ -902,7 +976,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
player.InGameClient.SendMessage(
new MessageSystem.Message.Definitions.Base.GenericBlobMessage(Opcodes.CurrencyDataFull)
{ Data = Moneys.Build().ToByteArray() });
{ Data = Moneys.Build().ToByteArray() });
}
public virtual void OnRequestUse(Player player, Item target, int actionId, WorldPlace worldPlace)
@ -1195,7 +1269,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
player.InGameClient.SendMessage(
new MessageSystem.Message.Definitions.Base.GenericBlobMessage(Opcodes.CurrencyDataFull)
{ Data = moneys.Build().ToByteArray() });
{ Data = moneys.Build().ToByteArray() });
player.Inventory.DestroyInventoryItem(this);
return;
@ -1353,7 +1427,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
player.Attributes[GameAttributes.Buff_Icon_Count0, powerId] = activated ? 0 : 1;
player.Attributes.BroadcastChangedIfRevealed();
player.Inventory.SendVisualInventory(player);
var dbToon = player.Toon.DBToon;
var dbToon = player.Toon.DbToon;
dbToon.WingsActive = player.CurrentWingsPowerId;
player.World.Game.GameDbSession.SessionUpdate(dbToon);
return;

View File

@ -18,6 +18,7 @@ using DiIiS_NA.GameServer.Core.Types.TagMap;
using DiIiS_NA.GameServer.MessageSystem;
using DiIiS_NA.LoginServer.Toons;
using DiIiS_NA.Core.Helpers.Math;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.GameServer.GSSystem.PlayerSystem;
namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
@ -1358,8 +1359,8 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
private static void RandomSetUnidentified(Item item) => item.Unidentified =
FastRandom.Instance.Chance(item.Name.Contains("unique", StringComparison.InvariantCultureIgnoreCase)
|| item.ItemDefinition.Quality is ItemTable.ItemQuality.Legendary or ItemTable.ItemQuality.Special or ItemTable.ItemQuality.Set
? GameServerConfig.Instance.ChanceHighQualityUnidentified
: GameServerConfig.Instance.ChanceNormalUnidentified);
? GameModsConfig.Instance.Items.UnidentifiedDropChances.HighQuality
: GameModsConfig.Instance.Items.UnidentifiedDropChances.NormalQuality);
// Allows cooking a custom item.
public static Item Cook(Player player, string name)

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using DiIiS_NA.D3_GameServer;
namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
{
@ -613,16 +614,16 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
switch (MonsterQuality)
{
case 0: //Normal
return new List<float> { 0.18f * GameServerConfig.Instance.RateChangeDrop };
return new List<float> { 0.18f * GameModsConfig.Instance.Rate.ChangeDrop };
case 1: //Champion
return new List<float> { 1f, 1f, 1f, 1f, 0.75f * GameServerConfig.Instance.RateChangeDrop };
return new List<float> { 1f, 1f, 1f, 1f, 0.75f * GameModsConfig.Instance.Rate.ChangeDrop };
case 2: //Rare (Elite)
case 4: //Unique
return new List<float> { 1f, 1f, 1f, 1f, 1f };
case 7: //Boss
return new List<float> { 1f, 1f, 1f, 1f, 1f, 0.75f * GameServerConfig.Instance.RateChangeDrop, 0.4f * GameServerConfig.Instance.RateChangeDrop };
return new List<float> { 1f, 1f, 1f, 1f, 1f, 0.75f * GameModsConfig.Instance.Rate.ChangeDrop, 0.4f * GameModsConfig.Instance.Rate.ChangeDrop };
default:
return new List<float> { 0.12f * GameServerConfig.Instance.RateChangeDrop };
return new List<float> { 0.12f * GameModsConfig.Instance.Rate.ChangeDrop };
}
}

View File

@ -24,6 +24,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using DiIiS_NA.D3_GameServer;
using Actor = DiIiS_NA.GameServer.GSSystem.ActorSystem.Actor;
namespace DiIiS_NA.GameServer.GSSystem.MapSystem
@ -549,7 +550,7 @@ namespace DiIiS_NA.GameServer.GSSystem.MapSystem
SceneSNO = SceneSNO.Id,
Transform = Transform,
WorldID = World.GlobalID,
MiniMapVisibility = GameServerConfig.Instance.ForceMinimapVisibility
MiniMapVisibility = GameModsConfig.Instance.Minimap.ForceVisibility
};
}

View File

@ -10,6 +10,7 @@ using DiIiS_NA.Core.Helpers.Math;
using DiIiS_NA.Core.Logging;
using DiIiS_NA.Core.MPQ;
using DiIiS_NA.Core.MPQ.FileFormats;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
using DiIiS_NA.GameServer.Core.Types.Math;
using DiIiS_NA.GameServer.Core.Types.QuadTrees;
@ -867,7 +868,7 @@ namespace DiIiS_NA.GameServer.GSSystem.MapSystem
player.GroundItems[item.GlobalID] = item;
DropItem(source, null, item);
if (source.Attributes[GameAttributes.Level] >= Program.MaxLevel)
if (source.Attributes[GameAttributes.Level] >= Program.MAX_LEVEL)
{
item = ItemGenerator.GenerateRandomCraftItem(player, 35);
if (item == null) return;
@ -930,7 +931,7 @@ namespace DiIiS_NA.GameServer.GSSystem.MapSystem
/// <param name="position">The position for drop.</param>
public void SpawnGold(Actor source, Player player, int Min = -1)
{
int amount = (int)(LootManager.GetGoldAmount(player.Attributes[GameAttributes.Level]) * Game.GoldModifier * GameServerConfig.Instance.RateMoney);
int amount = (int)(LootManager.GetGoldAmount(player.Attributes[GameAttributes.Level]) * Game.GoldModifier * GameModsConfig.Instance.Rate.Gold);
if (Min != -1)
amount += Min;
var item = ItemGenerator.CreateGold(player, amount); // somehow the actual ammount is not shown on ground /raist.

View File

@ -65,7 +65,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
// If there is no matching childnode, there must be one with -1 which only combines all class specific into one
private int GetDuration()
{
var node = currentLineNode.ChildNodes.FirstOrDefault(a => a.ClassFilter == player.Toon.VoiceClassID);
var node = currentLineNode.ChildNodes.FirstOrDefault(a => a.ClassFilter == player.Toon.VoiceClassId);
node ??= currentLineNode.ChildNodes.FirstOrDefault(a => a.ClassFilter == -1);
if (node == null)
@ -74,7 +74,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
}
return node.CompressedDisplayTimes[(int)manager.ClientLanguage]
.Languages[player.Toon.VoiceClassID * 2 + (player.Toon.Gender == 0 ? 0 : 1)];
.Languages[player.Toon.VoiceClassId * 2 + (player.Toon.Gender == 0 ? 0 : 1)];
}
// This returns the dynamicID of other conversation partners. The client uses its position to identify where you can hear the conversation.
@ -573,10 +573,10 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
LineID = currentLineNode.LineID,
Speaker = currentLineNode.LineSpeaker,
LineGender = -1,
AudioClass = (GameBalance.Class)player.Toon.VoiceClassID,
AudioClass = (GameBalance.Class)player.Toon.VoiceClassId,
Gender = (player.Toon.Gender == 0) ? VoiceGender.Male : VoiceGender.Female,
TextClass = currentLineNode.LineSpeaker == Speaker.Player
? (GameBalance.Class)player.Toon.VoiceClassID
? (GameBalance.Class)player.Toon.VoiceClassId
: GameBalance.Class.None,
SNOSpeakerActor = (int)GetSpeaker(currentLineNode.LineSpeaker).SNO,
LineFlags = 0x00000000,

View File

@ -582,7 +582,8 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
ChangeItemLocationDB(old_x, old_y + 1, addedItem);
destGrid.PlaceItem(addedItem, old_y + 1, old_x);
}
};
}
;
}
else
{
@ -1043,7 +1044,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
item.Owner = _owner;
InventoryGrid targetGrid = (msg.InvLoc.EquipmentSlot == (int)EquipmentSlotId.Stash) ? _stashGrid : _inventoryGrid;
SaveItemToDB(_owner.Toon.GameAccount.DBGameAccount, _owner.Toon.DBToon, EquipmentSlotId.Inventory, item);
SaveItemToDB(_owner.Toon.GameAccount.DBGameAccount, _owner.Toon.DbToon, EquipmentSlotId.Inventory, item);
ChangeItemLocationDB(msg.InvLoc.Column, msg.InvLoc.Row, item);
item.UpdateStackCount(amount);
targetGrid.PlaceItem(item, msg.InvLoc.Row, msg.InvLoc.Column);
@ -1198,12 +1199,12 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
(a.OverrideLevelReq <= Item.ItemDefinition.RequiredLevel)
);
if (!ReloadAffix.Definition.Name.Contains("Secondary")) filteredList = filteredList.Where( a => !a.Name.Contains("Secondary") );
if (!ReloadAffix.Definition.Name.Contains("Secondary")) filteredList = filteredList.Where(a => !a.Name.Contains("Secondary"));
if (!ReloadAffix.Definition.Name.Contains("Experience")) filteredList = filteredList.Where(a => !a.Name.Contains("Experience"));
if (!ReloadAffix.Definition.Name.Contains("Archon")) filteredList = filteredList.Where(a => !a.Name.Contains("Archon"));
// FIXME: always true?
if (ReloadAffix.Definition.Hash == ReloadAffix.Definition.Hash) filteredList = filteredList.Where(a => a.Hash != ReloadAffix.Definition.Hash);
if (Item.GBHandle.GBID == -4139386) filteredList = filteredList.Where( a => !a.Name.Contains("Str") && !a.Name.Contains("Dex") && !a.Name.Contains("Int") && !a.Name.Contains("Vit" ));
if (Item.GBHandle.GBID == -4139386) filteredList = filteredList.Where(a => !a.Name.Contains("Str") && !a.Name.Contains("Dex") && !a.Name.Contains("Int") && !a.Name.Contains("Vit"));
Dictionary<int, AffixTable> bestDefinitions = new Dictionary<int, AffixTable>();
@ -1557,7 +1558,8 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
return;
int idDuration = 60;
_owner.StartCasting(idDuration, new Action(() => {
_owner.StartCasting(idDuration, new Action(() =>
{
item.Identify();
}));
}
@ -1713,7 +1715,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
switch (ingr.ItemsGBID)
{
case -363607620: // Common parts.
if(_owner.Toon.GameAccount.CraftItem1 < ingr.Count)
if (_owner.Toon.GameAccount.CraftItem1 < ingr.Count)
haveEnoughIngredients = false;
break;
case -1585802162: // Wizard Dust.
@ -1754,7 +1756,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
_stashGrid.RemoveItem(item);
item.Unreveal(item.Owner as Player);
}
else if(item.Attributes[GameAttributes.ItemStackQuantityLo] > ingr.Count)
else if (item.Attributes[GameAttributes.ItemStackQuantityLo] > ingr.Count)
{
item.Attributes[GameAttributes.ItemStackQuantityLo] -= ingr.Count;
item.Attributes.BroadcastChangedIfRevealed();
@ -1773,13 +1775,13 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
switch (ingr.ItemsGBID)
{
case -363607620: // Common parts.
_owner.Toon.GameAccount.CraftItem1 -= ingr.Count;
_owner.Toon.GameAccount.CraftItem1 -= ingr.Count;
break;
case -1585802162: // Wizard Dust.
_owner.Toon.GameAccount.CraftItem2 -= ingr.Count;
_owner.Toon.GameAccount.CraftItem2 -= ingr.Count;
break;
case -605947593: // Blurred Crystal.
_owner.Toon.GameAccount.CraftItem3 -= ingr.Count;
_owner.Toon.GameAccount.CraftItem3 -= ingr.Count;
break;
}
}
@ -1800,7 +1802,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
}
//else
if (!(recipeDefinition.Name.StartsWith("T12_") || recipeDefinition.Name.StartsWith("T11_")))
reward.Attributes[GameAttributes.Item_Quality_Level] = Math.Min(recipe.ItemSpecifierData.AdditionalRandomAffixes + 2 , 9);
reward.Attributes[GameAttributes.Item_Quality_Level] = Math.Min(recipe.ItemSpecifierData.AdditionalRandomAffixes + 2, 9);
if (reward.Attributes[GameAttributes.Item_Quality_Level] < 9)
{
AffixGenerator.Generate(reward, recipe.ItemSpecifierData.AdditionalRandomAffixes, true);
@ -2318,7 +2320,12 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
D3.Items.CurrencyData craft7Data = D3.Items.CurrencyData.CreateBuilder().SetId(20).SetCount(playerAcc.BigPortalKey).Build(); // KeyStone Greater Rift.
D3.Items.CurrencyData[] consumables = {goldData, bloodShardData, platinumData, craft1Data, craft2Data, craft3Data, craft4Data, craft5Data, craft7Data, horadric1Data, horadric2Data, horadric3Data, horadric4Data, horadric5Data, craft8Data, craft9Data, craft10Data, craft11Data};
D3.Items.CurrencyData[] consumables = {
goldData, bloodShardData, platinumData, craft1Data,
craft2Data, craft3Data, craft4Data, craft5Data, craft7Data,
horadric1Data, horadric2Data, horadric3Data, horadric4Data,
horadric5Data, craft8Data, craft9Data, craft10Data, craft11Data
};
foreach (var consumable in consumables)
{
@ -2404,7 +2411,8 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
_owner.SetAttributesByParagon();
CheckWeapons();
_owner.Attributes.BroadcastChangedIfRevealed();
Task.Delay(3000).ContinueWith((t) => {
Task.Delay(3000).ContinueWith((t) =>
{
try
{
_owner.CheckBonusSets();
@ -2579,7 +2587,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
if (slotId == 15)
item.DBInventory.DBToon = null;
else
item.DBInventory.DBToon = (_owner as Player).Toon.DBToon;
item.DBInventory.DBToon = (_owner as Player).Toon.DbToon;
item.Owner.World.Game.GameDbSession.SessionUpdate(item.DBInventory);
//Logger.Debug("ChangeItemSlotDB success, item dbid: {0}", item.DBInventory.Id);

View File

@ -56,10 +56,12 @@ using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Pet;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Game;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Hireling;
using DiIiS_NA.Core.Helpers.Hash;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Encounter;
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
using DiIiS_NA.D3_GameServer.GSSystem.ActorSystem.Implementations.Artisans;
using DiIiS_NA.D3_GameServer.GSSystem.PlayerSystem;
using DiIiS_NA.LoginServer;
using NHibernate.Util;
namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem;
@ -221,9 +223,9 @@ public class Player : Actor, IMessageConsumer, IUpdateable
if (value == null)
{
HirelingId = null;
lock (Toon.DBToon)
lock (Toon.DbToon)
{
var dbToon = Toon.DBToon;
var dbToon = Toon.DbToon;
dbToon.ActiveHireling = null;
DBSessions.SessionUpdate(dbToon);
}
@ -231,9 +233,9 @@ public class Player : Actor, IMessageConsumer, IUpdateable
else if (value != _activeHireling)
{
HirelingId = value.Attributes[GameAttributes.Hireling_Class];
lock (Toon.DBToon)
lock (Toon.DbToon)
{
var dbToon = Toon.DBToon;
var dbToon = Toon.DbToon;
dbToon.ActiveHireling = value.Attributes[GameAttributes.Hireling_Class];
DBSessions.SessionUpdate(dbToon);
}
@ -292,10 +294,10 @@ public class Player : Actor, IMessageConsumer, IUpdateable
PlayerGroupIndex = InGameClient.Game.PlayerGroupIndexCounter;
Toon = bnetToon;
LevelingBoosted = Toon.LevelingBoosted;
var dbToon = Toon.DBToon;
var dbToon = Toon.DbToon;
HirelingId = dbToon.ActiveHireling;
GBHandle.Type = (int)ActorType.Player;
GBHandle.GBID = Toon.ClassID;
GBHandle.GBID = Toon.ClassId;
Level = dbToon.Level;
ParagonLevel = Toon.ParagonLevel;
ExperienceNext = Toon.ExperienceNext;
@ -355,7 +357,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
else if (InGameClient.Game.CurrentAct == 3000)
EnableStoneOfRecall();
var lores = UnserializeBytes(Toon.DBToon.Lore);
var lores = UnserializeBytes(Toon.DbToon.Lore);
var num = 0;
foreach (var lore in lores)
{
@ -1446,10 +1448,10 @@ public class Player : Actor, IMessageConsumer, IUpdateable
Attributes[GameAttributes.Casting_Speed] = 1f;
//Basic stats
Attributes[GameAttributes.Level_Cap] = Program.MaxLevel;
Attributes[GameAttributes.Level_Cap] = Program.MAX_LEVEL;
Attributes[GameAttributes.Level] = Level;
Attributes[GameAttributes.Alt_Level] = ParagonLevel;
if (Level == Program.MaxLevel)
if (Level == Program.MAX_LEVEL)
{
Attributes[GameAttributes.Alt_Experience_Next_Lo] = (int)(ExperienceNext % uint.MaxValue);
Attributes[GameAttributes.Alt_Experience_Next_Hi] = (int)(ExperienceNext / uint.MaxValue);
@ -2146,7 +2148,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
break;
#endregion
#endregion
}
}
@ -2194,7 +2196,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
// message.Amount have the value send to add on attr of Paragon tabs.
ParagonBonuses[bonus.Category * 4 + bonus.Index - 1] += (ushort)message.Amount;
var dbToon = Toon.DBToon;
var dbToon = Toon.DbToon;
dbToon.ParagonBonuses = ParagonBonuses;
World.Game.GameDbSession.SessionUpdate(dbToon);
@ -2212,7 +2214,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
{
ParagonBonuses = new ushort[]
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
var dbToon = Toon.DBToon;
var dbToon = Toon.DbToon;
dbToon.ParagonBonuses = ParagonBonuses;
World.Game.GameDbSession.SessionUpdate(dbToon);
@ -2480,6 +2482,21 @@ public class Player : Actor, IMessageConsumer, IUpdateable
public bool SpeedCheckDisabled = false;
public float StrengthMultiplier => ParagonLevel > 0
? GameModsConfig.Instance.Player.Multipliers.Strength.Paragon
: GameModsConfig.Instance.Player.Multipliers.Strength.Normal;
public float DexterityMultiplier => ParagonLevel > 0
? GameModsConfig.Instance.Player.Multipliers.Dexterity.Paragon
: GameModsConfig.Instance.Player.Multipliers.Dexterity.Normal;
public float IntelligenceMultiplier => ParagonLevel > 0
? GameModsConfig.Instance.Player.Multipliers.Intelligence.Paragon
: GameModsConfig.Instance.Player.Multipliers.Intelligence.Normal;
public float VitalityMultiplier => ParagonLevel > 0
? GameModsConfig.Instance.Player.Multipliers.Vitality.Paragon
: GameModsConfig.Instance.Player.Multipliers.Intelligence.Normal;
public static byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
@ -2674,8 +2691,9 @@ public class Player : Actor, IMessageConsumer, IUpdateable
Logger.WarnException(e, "questEvent()");
}
}
// Reset resurrection charges on zone change - TODO: do not reset charges on reentering the same zone
Attributes[GameAttributes.Corpse_Resurrection_Charges] = GameServerConfig.Instance.ResurrectionCharges;
// Reset resurrection charges on zone change
// TODO: do not reset charges on reentering the same zone
Attributes[GameAttributes.Corpse_Resurrection_Charges] = GameModsConfig.Instance.Health.ResurrectionCharges;
#if DEBUG
Logger.Warn($"Player Location {Toon.Name}, Scene: {CurrentScene.SceneSNO.Name} SNO: {CurrentScene.SceneSNO.Id} LevelArea: {CurrentScene.Specification.SNOLevelAreas[0]}");
@ -2884,7 +2902,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
//*/
private void OnEquipPotion(GameClient client, ChangeUsableItemMessage message)
{
var activeSkills = Toon.DBActiveSkills;
var activeSkills = Toon.DbActiveSkills;
activeSkills.PotionGBID = message.Field1;
World.Game.GameDbSession.SessionUpdate(activeSkills);
}
@ -3197,7 +3215,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
});
World.Leave(skeleton);
}
catch{}
catch { }
}
NecromancerSkeletons.Clear();
@ -3604,8 +3622,13 @@ public class Player : Actor, IMessageConsumer, IUpdateable
System.Threading.Tasks.Task.Delay(3).Wait();
RevealActorsToPlayer();
if (!_motdSent && LoginServer.LoginServerConfig.Instance.MotdEnabled)
{
if (!LoginServerConfig.Instance.MotdEnabledWhenWorldLoads)
_motdSent = true;
InGameClient.BnetClient.SendMotd();
}
//
}
@ -3989,10 +4012,10 @@ public class Player : Actor, IMessageConsumer, IUpdateable
get
{
var baseStrength = 0.0f;
var multiplier = ParagonLevel > 0 ? GameServerConfig.Instance.StrengthParagonMultiplier : GameServerConfig.Instance.StrengthMultiplier;
var multiplier = StrengthMultiplier;
baseStrength = Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Strength
? Toon.HeroTable.Strength + (Level - 1) * 3
: Toon.HeroTable.Strength + (Level - 1);
? Toon.HeroTable.Strength + (Level - 1) * 3
: Toon.HeroTable.Strength + (Level - 1);
return baseStrength * multiplier;
}
@ -4005,8 +4028,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
{
get
{
var multiplier = ParagonLevel > 0 ? GameServerConfig.Instance.DexterityParagonMultiplier : GameServerConfig.Instance.DexterityMultiplier;
var multiplier = DexterityMultiplier;
return Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Dexterity
? Toon.HeroTable.Dexterity + (Level - 1) * 3 * multiplier
: Toon.HeroTable.Dexterity + (Level - 1) * multiplier;
@ -4016,7 +4038,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
public float TotalDexterity =>
Attributes[GameAttributes.Dexterity] + Inventory.GetItemBonus(GameAttributes.Dexterity_Item);
public float Vitality => Toon.HeroTable.Vitality + (Level - 1) * 2 * (ParagonLevel > 0 ? GameServerConfig.Instance.VitalityParagonMultiplier : GameServerConfig.Instance.VitalityMultiplier);
public float Vitality => Toon.HeroTable.Vitality + (Level - 1) * 2 * (VitalityMultiplier);
public float TotalVitality =>
Attributes[GameAttributes.Vitality] + Inventory.GetItemBonus(GameAttributes.Vitality_Item);
@ -4025,7 +4047,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
{
get
{
var multiplier = ParagonLevel > 0 ? GameServerConfig.Instance.IntelligenceParagonMultiplier : GameServerConfig.Instance.IntelligenceMultiplier;
var multiplier = IntelligenceMultiplier;
return Toon.HeroTable.CoreAttribute == GameBalance.PrimaryAttribute.Intelligence
? Toon.HeroTable.Intelligence + (Level - 1) * 3 * multiplier
: Toon.HeroTable.Intelligence + (Level - 1) * multiplier;
@ -4076,7 +4098,9 @@ public class Player : Actor, IMessageConsumer, IUpdateable
HotBarButtons = SkillSet.HotBarSkills,
HotBarButton = new HotbarButtonData
{
SNOSkill = -1, RuneType = -1, ItemGBId =
SNOSkill = -1,
RuneType = -1,
ItemGBId =
StringHashHelper.HashItemName(
"HealthPotionBottomless") //2142362846//this.Toon.DBActiveSkills.PotionGBID
,
@ -4084,7 +4108,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
},
SkillSlotEverAssigned = 0x0F, //0xB4,
PlaytimeTotal = Toon.TimePlayed,
WaypointFlags = GameServerConfig.Instance.UnlockAllWaypoints ? 0x0000ffff : World.Game.WaypointFlags,
WaypointFlags = GameModsConfig.Instance.Quest.UnlockAllWaypoints ? 0x0000ffff : World.Game.WaypointFlags,
HirelingData = new HirelingSavedData()
{
HirelingInfos = HirelingInfo,
@ -4203,7 +4227,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
serialized += Inventory.GetItemBonus(GameAttributes.Armor_Item).ToString("F0");
serialized += ";";
serialized += totalDamage.ToString("F0");
var dbStats = Toon.DBToon;
var dbStats = Toon.DbToon;
dbStats.Stats = serialized;
World.Game.GameDbSession.SessionUpdate(dbStats);
}
@ -4300,7 +4324,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
else
{
bonusSet.Claimed = true;
bonusSet.ClaimedToon = Toon.DBToon;
bonusSet.ClaimedToon = Toon.DbToon;
}
//BonusSetsList.CollectionEditions[bonusSet.SetId].Claim(this);
@ -4318,8 +4342,14 @@ public class Player : Actor, IMessageConsumer, IUpdateable
//returns empty data
var emptyHireling = new HirelingInfo
{
HirelingIndex = type, GbidName = 0x0000, Dead = false, Skill1SNOId = -1, Skill2SNOId = -1,
Skill3SNOId = -1, Skill4SNOId = -1, annItems = -1
HirelingIndex = type,
GbidName = 0x0000,
Dead = false,
Skill1SNOId = -1,
Skill2SNOId = -1,
Skill3SNOId = -1,
Skill4SNOId = -1,
annItems = -1
};
return emptyHireling;
}
@ -4477,18 +4507,18 @@ public class Player : Actor, IMessageConsumer, IUpdateable
if (BlacksmithUnlocked || InGameClient.Game.CurrentAct == 3000)
InGameClient.SendMessage(new GenericBlobMessage(Opcodes.CraftingDataBlacksmithInitialMessage)
{ Data = blacksmith.ToByteArray() });
{ Data = blacksmith.ToByteArray() });
if (JewelerUnlocked || InGameClient.Game.CurrentAct == 3000)
InGameClient.SendMessage(new GenericBlobMessage(Opcodes.CraftingDataJewelerInitialMessage)
{ Data = jeweler.ToByteArray() });
{ Data = jeweler.ToByteArray() });
if (MysticUnlocked || InGameClient.Game.CurrentAct == 3000)
{
InGameClient.SendMessage(new GenericBlobMessage(Opcodes.CraftingDataMysticInitialMessage)
{ Data = mystic.ToByteArray() });
{ Data = mystic.ToByteArray() });
InGameClient.SendMessage(new GenericBlobMessage(Opcodes.CraftingDataTransmogInitialMessage)
{ Data = transmog.ToByteArray() });
{ Data = transmog.ToByteArray() });
}
}
@ -4507,8 +4537,8 @@ public class Player : Actor, IMessageConsumer, IUpdateable
foreach (var mail in mailData)
{
var mailRow = D3.Items.Mail.CreateBuilder()
.SetAccountTo(Toon.D3EntityID)
.SetAccountFrom(Toon.D3EntityID)
.SetAccountTo(Toon.D3EntityId)
.SetAccountFrom(Toon.D3EntityId)
.SetMailId(mail.Id)
.SetTitle(mail.Title)
.SetBody(mail.Body);
@ -4587,7 +4617,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
private readonly Dictionary<ulong, uint> _achievementCounters = new();
public int DodgesInARow { get; set; } = 0;
public int BlocksInARow { get; set; }= 0;
public int BlocksInARow { get; set; } = 0;
public void GrantAchievement(ulong id)
{
@ -5092,13 +5122,15 @@ public class Player : Actor, IMessageConsumer, IUpdateable
if (World.Game.IsHardcore && Attributes[GameAttributes.Level] >= 70)
addedExp *= 5;
if (Attributes[GameAttributes.Alt_Level] >= 515)
{
var XPcap = 91.262575239831f * Math.Pow(Attributes[GameAttributes.Alt_Level], 3) -
44301.083380565047f * Math.Pow(Attributes[GameAttributes.Alt_Level], 2) +
3829010.395566940308f * Attributes[GameAttributes.Alt_Level] + 322795582.543823242188f;
addedExp = (int)((float)(ParagonLevelBorders[Attributes[GameAttributes.Alt_Level]] / XPcap) * addedExp);
}
// To'do verify this formula.
// Remove this if to remove paragon level cap.
//if (Attributes[GameAttributes.Alt_Level] >= 515)
//{
// var XPcap = 91.262575239831f * Math.Pow(Attributes[GameAttributes.Alt_Level], 3) -
// 44301.083380565047f * Math.Pow(Attributes[GameAttributes.Alt_Level], 2) +
// 3829010.395566940308f * Attributes[GameAttributes.Alt_Level] + 322795582.543823242188f;
// addedExp = (int)((float)(ParagonLevelBorders[Attributes[GameAttributes.Alt_Level]] / XPcap) * addedExp);
//}
if (Attributes[GameAttributes.Rest_Experience_Lo] > 0)
{
@ -5493,7 +5525,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
{
if (InGameClient.Game.ActiveNephalemTimer && InGameClient.Game.ActiveNephalemKilledMobs == false)
{
InGameClient.Game.ActiveNephalemProgress += 15f * GameServerConfig.Instance.NephalemRiftProgressMultiplier;
InGameClient.Game.ActiveNephalemProgress += 15f * GameModsConfig.Instance.NephalemRift.ProgressMultiplier;
foreach (var plr in InGameClient.Game.Players.Values)
{
plr.InGameClient.SendMessage(new FloatDataMessage(Opcodes.DunggeonFinderProgressGlyphPickUp)
@ -5563,9 +5595,9 @@ public class Player : Actor, IMessageConsumer, IUpdateable
});
plr.InGameClient.SendMessage(new DisplayGameTextMessage(Opcodes.DisplayGameChatTextMessage)
{ Message = "Messages:LR_BossSpawned" });
{ Message = "Messages:LR_BossSpawned" });
plr.InGameClient.SendMessage(new DisplayGameTextMessage(Opcodes.DisplayGameTextMessage)
{ Message = "Messages:LR_BossSpawned" });
{ Message = "Messages:LR_BossSpawned" });
}
StartConversation(World, 366542);
@ -5665,40 +5697,40 @@ public class Player : Actor, IMessageConsumer, IUpdateable
case 0:
return;
case > 0:
{
if (Attributes[GameAttributes.Hitpoints_Cur] < Attributes[GameAttributes.Hitpoints_Max_Total])
{
if (Toon.Class == ToonClass.Barbarian)
if (SkillSet.HasPassive(205217))
quantity += 0.01f * Attributes[GameAttributes.Health_Globe_Bonus_Health];
if (guidingLight) //Monk -> Guiding Light
if (Attributes[GameAttributes.Hitpoints_Cur] < Attributes[GameAttributes.Hitpoints_Max_Total])
{
var missingHP =
(Attributes[GameAttributes.Hitpoints_Max_Total] - Attributes[GameAttributes.Hitpoints_Cur]) /
Attributes[GameAttributes.Hitpoints_Max_Total];
if (missingHP > 0.05f)
if (!World.BuffManager.HasBuff<GuidingLightBuff>(this))
World.BuffManager.AddBuff(this, this,
new GuidingLightBuff(Math.Min(missingHP, 0.3f),
TickTimer.WaitSeconds(World.Game, 10.0f)));
if (Toon.Class == ToonClass.Barbarian)
if (SkillSet.HasPassive(205217))
quantity += 0.01f * Attributes[GameAttributes.Health_Globe_Bonus_Health];
if (guidingLight) //Monk -> Guiding Light
{
var missingHP =
(Attributes[GameAttributes.Hitpoints_Max_Total] - Attributes[GameAttributes.Hitpoints_Cur]) /
Attributes[GameAttributes.Hitpoints_Max_Total];
if (missingHP > 0.05f)
if (!World.BuffManager.HasBuff<GuidingLightBuff>(this))
World.BuffManager.AddBuff(this, this,
new GuidingLightBuff(Math.Min(missingHP, 0.3f),
TickTimer.WaitSeconds(World.Game, 10.0f)));
}
Attributes[GameAttributes.Hitpoints_Cur] = Math.Min(
Attributes[GameAttributes.Hitpoints_Cur] + quantity,
Attributes[GameAttributes.Hitpoints_Max_Total]);
Attributes.BroadcastChangedIfRevealed();
InGameClient.SendMessage(new FloatingNumberMessage
{
ActorID = DynamicID(this),
Number = quantity,
Type = FloatingNumberMessage.FloatType.Green
});
}
Attributes[GameAttributes.Hitpoints_Cur] = Math.Min(
Attributes[GameAttributes.Hitpoints_Cur] + quantity,
Attributes[GameAttributes.Hitpoints_Max_Total]);
Attributes.BroadcastChangedIfRevealed();
InGameClient.SendMessage(new FloatingNumberMessage
{
ActorID = DynamicID(this),
Number = quantity,
Type = FloatingNumberMessage.FloatType.Green
});
break;
}
break;
}
default:
Attributes[GameAttributes.Hitpoints_Cur] = Math.Max(
Attributes[GameAttributes.Hitpoints_Cur] + quantity,
@ -5961,7 +5993,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable
LearnedLore.Count++; // Count
UpdateHeroState();
Logger.Trace("Learning lore #{0}", loreSNOId);
var dbToon = Toon.DBToon;
var dbToon = Toon.DbToon;
dbToon.Lore = SerializeBytes(LearnedLore.m_snoLoreLearned.Take(LearnedLore.Count).ToList());
World.Game.GameDbSession.SessionUpdate(dbToon);
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.GameServer.GSSystem.PlayerSystem;
using DiIiS_NA.GameServer.GSSystem.TickerSystem;
using DiIiS_NA.LoginServer;
@ -12,8 +13,8 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations.General
public override IEnumerable<TickTimer> Run()
{
if (User is not Player player) yield break;
player.AddPercentageHP(GameServerConfig.Instance.HealthPotionRestorePercentage);
AddBuff(player, player, new CooldownBuff(30211, TickTimer.WaitSeconds(player.World.Game, GameServerConfig.Instance.HealthPotionCooldown)));
player.AddPercentageHP(GameModsConfig.Instance.Health.PotionRestorePercentage);
AddBuff(player, player, new CooldownBuff(30211, TickTimer.WaitSeconds(player.World.Game, GameModsConfig.Instance.Health.PotionCooldown)));
}
}
}

View File

@ -148,7 +148,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations
if (Rune_B > 0) DmgType = DamageType.Lightning; //Electrify
AttackPayload attack = new AttackPayload(this);
attack.Targets = GetEnemiesInArcDirection(User.Position, TargetPosition, 12f, Rune_D > 0 ? 120f : 90f); //Carve
if (Rune_C > 0) attack.chcBonus = ScriptFormula(14); //Crush
if (Rune_C > 0) attack.ChcBonus = ScriptFormula(14); //Crush
attack.AddWeaponDamage(ScriptFormula(0), DmgType);
attack.OnHit = hitPayload =>
{
@ -3150,7 +3150,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations
attack.Targets = GetEnemiesInRadius(point, 12f);
attack.AddWeaponDamage(ScriptFormula(3), DamageType.Physical);
if (Rune_B > 0) //Annihilate
attack.chcBonus = 1f; //will be capped to 85% anyway
attack.ChcBonus = 1f; //will be capped to 85% anyway
attack.OnHit = (hitPayload) =>
{
if (Rune_A > 0) //Barrels of tar

View File

@ -1264,7 +1264,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations
}
}
#endregion
//Done
//Done - testing, apparently Rune_A not working.
#region CorpseExlosion
[ImplementsPowerSNO(SkillsSystem.Skills.Necromancer.ExtraSkills.CorpseExlosion)]
@ -1272,72 +1272,86 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations
{
public override IEnumerable<TickTimer> Main()
{
//ScriptFormulaDetails_Fields
//PowerDefinition_Fields
//Мертвячинка) - if (player.SkillSet.HasPassive(208594)) 454066
if (Rune_B > 0)
((Player) User).AddPercentageHP(-2);
float Radius = 20f;
float Damage = 10.5f;
DamageType DType = DamageType.Physical;
var PowerData = (DiIiS_NA.Core.MPQ.FileFormats.Power)MPQStorage.Data.Assets[SNOGroup.Power][PowerSNO].Data;
var Point = SpawnEffect(ActorSno._p6_necro_bonespikes, TargetPosition, 0, WaitSeconds(0.2f));
Point.PlayEffect(Effect.PlayEffectGroup, RuneSelect(459954, 473926, 459954, 473907, 459954//D
, 473864));
// Initializing main variables for Bonespikes ability.
float radius = 20f;
float damage = 10.5f;
DamageType damageType = DamageType.Physical;
// Fetching the data for the respective Power from the MPQ cache.
var powerData = (DiIiS_NA.Core.MPQ.FileFormats.Power)MPQStorage.Data.Assets[SNOGroup.Power][PowerSNO].Data;
// Creating a point effect on the target position, playing various effect groups depending on the selected Rune.
var point = SpawnEffect(ActorSno._p6_necro_bonespikes, TargetPosition, 0, WaitSeconds(0.2f));
point.PlayEffect(Effect.PlayEffectGroup, RuneSelect(459954, 473926, 459954, 473907, 459954, 473864));
// Depending on a specific game attribute, either spawn a new monster at the target position, or select up to five existing corpses.
var actors = User.Attributes[GameAttributes.Necromancer_Corpse_Free_Casting]
? new List<uint> { User.World.SpawnMonster(ActorSno._p6_necro_corpse_flesh, TargetPosition).GlobalID }
: User.GetActorsInRange(TargetPosition, 11).Where(x => x.SNO == ActorSno._p6_necro_corpse_flesh).Select(x => x.GlobalID).Take(5).ToList();
if (Rune_D > 0)
Radius = 25f;
else if (Rune_C > 0)//licking action
{ Damage = 15.75f; DType = DamageType.Poison; }
else if (Rune_A > 0)
DType = DamageType.Poison;
: User.GetActorsInRange(TargetPosition, 11).Where(x => x.SNO == ActorSno._p6_necro_corpse_flesh)
.Select(x => x.GlobalID).Take(5).ToList();
// Modifying main parameters of the ability depending on the selected Rune.
if (Rune_D > 0)
{
radius = 25f;
}
else if (Rune_C > 0) // Licking action.
{
damage = 15.75f;
damageType = DamageType.Poison;
}
else if (Rune_A > 0)
{
damageType = DamageType.Poison;
}
// Applying the effects of the Bonespikes ability on the selected corpses.
foreach (var actor in actors)
{
if (Rune_B > 0)
{
var bomb = World.GetActorByGlobalId(actor);
var nearestEnemy = bomb.GetActorsInRange(20f).First();
if (nearestEnemy != null)
bomb.Teleport(nearestEnemy.Position);
}
var Explosion = SpawnEffect(
// Spawning explosion effect.
var explosionEffect = SpawnEffect(
ActorSno._p6_necro_corpseexplosion_projectile_spawn,
World.GetActorByGlobalId(actor).Position,
ActorSystem.Movement.MovementHelpers.GetFacingAngle(User, World.GetActorByGlobalId(actor)),
WaitSeconds(0.2f)
);
Explosion.PlayEffect(Effect.PlayEffectGroup, RuneSelect(457183, 471539, 471258, 471249, 471247, 471236));
explosionEffect.PlayEffect(Effect.PlayEffectGroup,
RuneSelect(457183, 471539, 471258, 471249, 471247, 471236));
explosionEffect.UpdateDelay = 0.1f;
Explosion.UpdateDelay = 0.1f;
Explosion.OnUpdate = () =>
explosionEffect.OnUpdate = () =>
{
AttackPayload attack = new AttackPayload(this)
// Creating the attack payload.
AttackPayload attack = new(this)
{
Targets = GetEnemiesInRadius(User.Position, Radius)
Targets = GetEnemiesInRadius(User.Position, radius)
};
if (Rune_E > 0)
DType = DamageType.Cold;
damageType = DamageType.Cold;
attack.AddWeaponDamage(Damage, DType);
// Applying weapon damage.
attack.AddWeaponDamage(damage, damageType);
attack.OnHit = hitPayload =>
{
if (Rune_E > 0)
AddBuff(hitPayload.Target, new DebuffFrozen(WaitSeconds(2f)));
};
// Applying the attack.
attack.Apply();
};
// Destroying the selected corpse.
World.GetActorByGlobalId(actor).Destroy();
}
//});
yield break;
}
}

View File

@ -16,7 +16,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
// list of targets to try and hit with this payload, must be set before calling Apply()
public TargetList Targets;
public float chcBonus = 0f;
public float ChcBonus = 0f;
// list of each amount and type of damage the attack will contain
public class DamageEntry
@ -115,7 +115,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
if (target == null || target.World == null || target.World != null && target.World.PowerManager.IsDeletingActor(target))
continue;
var payload = new HitPayload(this, _DoCriticalHit(Context.User, target, chcBonus)
var payload = new HitPayload(this, _DoCriticalHit(Context.User, target, ChcBonus)
, target);
payload.AutomaticHitEffects = AutomaticHitEffects;
payload.OnDeath = OnDeath;

View File

@ -14,6 +14,7 @@ using DiIiS_NA.GameServer.GSSystem.PowerSystem.Implementations;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Effect;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Combat;
using DiIiS_NA.Core.Helpers.Math;
using DiIiS_NA.D3_GameServer;
using DiIiS_NA.LoginServer.Toons;
using DiIiS_NA.GameServer.Core.Types.TagMap;
using DiIiS_NA.GameServer.GSSystem.GeneratorsSystem;
@ -27,7 +28,7 @@ using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Quest;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.World;
using DiIiS_NA.GameServer.MessageSystem.Message.Fields;
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
using static DiIiS_NA.Core.MPQ.FileFormats.Monster.MonsterType;
namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
{
public class DeathPayload : Payload
@ -123,7 +124,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
}
if (Target is Minion { Master: Player masterPlr2 }
and (BaseGolem or IceGolem or BoneGolem or DecayGolem or ConsumeFleshGolem or BloodGolem))
and (BaseGolem or IceGolem or BoneGolem or DecayGolem or ConsumeFleshGolem or DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations.Minions.BloodGolem))
{
masterPlr2.InGameClient.SendMessage(new MessageSystem.Message.Definitions.Pet.PetDetachMessage()
{
@ -425,7 +426,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
{
grantedExp = (int)(grantedExp * rangedPlayer.World.Game.XpModifier);
float tempExp = grantedExp * GameServerConfig.Instance.RateExp;
float tempExp = grantedExp * GameModsConfig.Instance.Rate.Experience;
rangedPlayer.UpdateExp(Math.Max((int)tempExp, 1));
var a = (int)rangedPlayer.Attributes[GameAttributes.Experience_Bonus];
@ -444,7 +445,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
{
if (rangedPlayer.Toon.Class == ToonClass.DemonHunter)
{
if (monster.MonsterType == (int)DiIiS_NA.Core.MPQ.FileFormats.Monster.MonsterType.Demon)
if (monster.MonsterTypeValue == (int)DiIiS_NA.Core.MPQ.FileFormats.Monster.MonsterType.Demon)
rangedPlayer.AddAchievementCounter(74987243307065, 1);
if (PowerMath.Distance2D(rangedPlayer.Position, monster.Position) >= 45f)
@ -636,13 +637,13 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
Target.World.Game.ActiveNephalemTimer && Target.World.Game.ActiveNephalemKilledMobs == false)
{
Target.World.Game.ActiveNephalemProgress +=
GameServerConfig.Instance.NephalemRiftProgressMultiplier * (Target.Quality + 1);
GameModsConfig.Instance.NephalemRift.ProgressMultiplier * (Target.Quality + 1);
Player master = null;
foreach (var plr3 in Target.World.Game.Players.Values)
{
if (plr3.PlayerIndex == 0)
master = plr3;
if (GameServerConfig.Instance.NephalemRiftAutoFinish && Target.World.Monsters.Count(s => !s.Dead) <= GameServerConfig.Instance.NephalemRiftAutoFinishThreshold) Target.World.Game.ActiveNephalemProgress = 651;
if (GameModsConfig.Instance.NephalemRift.AutoFinish && Target.World.Monsters.Count(s => !s.Dead) <= GameModsConfig.Instance.NephalemRift.AutoFinishThreshold) Target.World.Game.ActiveNephalemProgress = 651;
plr3.InGameClient.SendMessage(new SimpleMessage(Opcodes.KillCounterRefresh));
plr3.InGameClient.SendMessage(new FloatDataMessage(Opcodes.DungeonFinderProgressMessage)
{
@ -711,7 +712,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
}
if (Target.Quality > 1 || FastRandom.Instance.Chance(GameServerConfig.Instance.NephalemRiftOrbsChance))
if (Target.Quality > 1 || FastRandom.Instance.Chance(GameModsConfig.Instance.NephalemRift.OrbsChance))
{
//spawn spheres for mining indicator
for (int i = 0; i < Target.Quality + 1; i++)
@ -938,7 +939,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
// if seed is less than the drop rate, drop the item
if (seed < rate * (1f
+ lootSpawnPlayer.Attributes[GameAttributes.Magic_Find])
* GameServerConfig.Instance.RateDrop)
* GameModsConfig.Instance.Rate.Drop)
{
//Logger.Debug("rate: {0}", rate);
var lootQuality = Target.World.Game.IsHardcore
@ -1223,7 +1224,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
if (player.World.Game.IsHardcore)
{
player.AddTimedAction(3f, (_) => player.Revive(player.CheckPointPosition));
var toon = player.Toon.DBToon;
var toon = player.Toon.DbToon;
toon.Deaths++;
player.World.Game.GameDbSession.SessionUpdate(toon);
}

View File

@ -376,7 +376,7 @@ namespace DiIiS_NA.GameServer.GSSystem.PowerSystem.Payloads
if (Target is Monster monster)
{
TotalDamage *= 1 + plr.Attributes[GameAttributes.Damage_Percent_Bonus_Vs_Monster_Type, monster.MonsterType];
TotalDamage *= 1 + plr.Attributes[GameAttributes.Damage_Percent_Bonus_Vs_Monster_Type, monster.MonsterTypeValue];
if (monster.Quality > 0)
TotalDamage *= 1 + plr.Attributes[GameAttributes.Damage_Percent_Bonus_Vs_Elites];

View File

@ -244,15 +244,22 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
//AddFollower(this.Game.GetWorld(71150), 4580);
Game.AddOnLoadWorldAction(WorldSno.trout_town, () =>
{
// TODO: CHeck for possible removing outer adding
Game.AddOnLoadWorldAction(WorldSno.trout_town, () =>
if (Game.CurrentQuest == 72095 && Game.CurrentStep is -1 or 7)
{
if (Game.CurrentQuest == 72095)
if (Game.CurrentStep == -1 || Game.CurrentStep == 7)
{
AddFollower(Game.GetWorld(WorldSno.trout_town), ActorSno._leah);
}
});
// var world = Game.GetWorld(WorldSno.trout_town);
// Logger.QuestStep("Adding leah follower");
// // teleport leah
// var actor = world.GetActorBySNO(ActorSno._leah);
// if (actor != null)
// {
// actor.Teleport(Game.FirstPlayer().Position.Around(2f));
// }
AddUniqueFollower(Game.GetWorld(WorldSno.trout_town), ActorSno._leah);
}
else
{
Logger.QuestStep($"Can't add leah follower: {Game.CurrentQuest} / {Game.CurrentStep}");
}
});
}
@ -265,6 +272,7 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
NextStep = 49,
OnAdvance = () =>
{ //go to gates
AddUniqueFollower(Game.GetWorld(WorldSno.trout_town), ActorSno._leah);
var world = Game.GetWorld(WorldSno.trout_town);
StartConversation(world, 166678);
ListenProximity(ActorSno._trout_oldtristram_exit_gate, new Advance());
@ -409,12 +417,16 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
Saveable = true,
NextStep = 23,
OnAdvance = () =>
{ //go to church
{
//go to church
var world = Game.GetWorld(WorldSno.trout_town);
ListenProximity(ActorSno._trdun_cath_cathedraldoorexterior, new Advance());
var leah = world.GetActorBySNO(ActorSno._leah);
if (leah != null)
{
leah.Hidden = false;
leah.SetVisible(true);
}
SetActorVisible(world, ActorSno._tristram_mayor, false);
var cart = world.GetActorBySNO(ActorSno._trout_newtristram_blocking_cart, true);
if (cart != null)
@ -482,6 +494,7 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
OnAdvance = () =>
{ //go with Cain
Game.CurrentEncounter.Activated = false;
StartConversation(Game.GetWorld(WorldSno.trdun_cain_intro), 72496);
ListenTeleport(19938, new Advance());
}
@ -504,19 +517,17 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
StartConversation(tristramWorld, 72498);
});
//StartConversation(this.Game.GetWorld(71150), 72496);
var leah = tristramWorld.GetActorBySNO(ActorSno._leah, true);
if (leah == null)
DestroyFollower(ActorSno._leah);
var leah = tristramWorld.GetActorsBySNO(ActorSno._leah);
if (!leah.Any())
{
leah = tristramWorld.GetActorBySNO(ActorSno._leah, false);
if (leah != null)
{
leah.Hidden = false;
leah.SetVisible(true);
}
else
{
Logger.Warn($"Leah not found in world {tristramWorld.SNO.ToString()} - quest 72095/step 32");
}
Logger.Warn("Leah not found in world.");
}
foreach (var l in leah)
{
l.Hidden = false;
l.SetVisible(true);
}
ListenConversation(198617, new Advance());
}

View File

@ -276,6 +276,12 @@ namespace DiIiS_NA.GameServer.GSSystem.QuestSystem
return Game.Players.Values.First().Followers.Any(x => x.Value == sno);
}
public void AddUniqueFollower(World world, ActorSno sno)
{
if (!HasFollower(sno))
AddFollower(world, sno);
}
public void AddFollower(World world, ActorSno sno)
{
if (Game.Players.Count > 0)

View File

@ -26,7 +26,7 @@ namespace DiIiS_NA.GameServer.GSSystem.SkillsSystem
ToonClass = toonClass;
Player = player;
// var dbToon = player.Toon.DBToon;
var dbActiveSkills = player.Toon.DBActiveSkills;
var dbActiveSkills = player.Toon.DbActiveSkills;
ActiveSkills = new ActiveSkillSavedData[6]
{
new()
@ -82,7 +82,7 @@ namespace DiIiS_NA.GameServer.GSSystem.SkillsSystem
public void UpdateSkills(int hotBarIndex, int SNOSkill, int SNORune, Toon toon)
{
Logger.MethodTrace(string.Format("Update index {0} skill {1} rune {2}", hotBarIndex, SNOSkill, SNORune));
var dbActiveSkills = Player.Toon.DBActiveSkills;
var dbActiveSkills = Player.Toon.DbActiveSkills;
switch (hotBarIndex)
{
case 0:
@ -127,7 +127,7 @@ namespace DiIiS_NA.GameServer.GSSystem.SkillsSystem
public void UpdatePassiveSkills(Toon toon)
{
Logger.Debug("Update passive to {0} {1} {2} {3}", PassiveSkills[0], PassiveSkills[1], PassiveSkills[2], PassiveSkills[3]);
var dbActiveSkills = Player.Toon.DBActiveSkills;
var dbActiveSkills = Player.Toon.DbActiveSkills;
dbActiveSkills.Passive0 = PassiveSkills[0];
dbActiveSkills.Passive1 = PassiveSkills[1];
dbActiveSkills.Passive2 = PassiveSkills[2];

View File

@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Threading.Tasks;
using DiIiS_NA;
using DiIiS_NA.Core.Logging;
using DiIiS_NA.GameServer;
using Newtonsoft.Json;
namespace DiIiS_NA.D3_GameServer;
public class RateConfig
{
public float GetDamageByDifficulty(int diff)
{
diff = Math.Clamp(diff, 0, 19);
return !DamageByDifficulty.ContainsKey(diff) ? 1f : DamageByDifficulty[diff];
}
public Dictionary<int, float> HealthByDifficulty { get; set; } = new()
{
[0] = 1.0f, [1] = 1.0f, [2] = 1.0f, [3] = 1.0f, [4] = 1.0f, [5] = 1.0f,
[6] = 1.0f, [7] = 1.0f, [8] = 1.0f, [9] = 1.0f, [10] = 1.0f, [11] = 1.0f,
[12] = 1.0f, [13] = 1.0f, [14] = 1.0f, [15] = 1.0f, [16] = 1.0f,
[17] = 1.0f, [18] = 1.0f, [19] = 1.0f,
};
public Dictionary<int, float> DamageByDifficulty { get; set; } = new()
{
[0] = 1.0f, [1] = 1.0f, [2] = 1.0f, [3] = 1.0f, [4] = 1.0f, [5] = 1.0f,
[6] = 1.0f, [7] = 1.0f, [8] = 1.0f, [9] = 1.0f, [10] = 1.0f, [11] = 1.0f,
[12] = 1.0f, [13] = 1.0f, [14] = 1.0f, [15] = 1.0f, [16] = 1.0f,
[17] = 1.0f, [18] = 1.0f, [19] = 1.0f,
};
public float Experience { get; set; } = 1;
public float Gold { get; set; } = 1;
public float Drop { get; set; } = 1;
public float ChangeDrop { get; set; } = 1;
}
public class HealthConfig
{
public float PotionRestorePercentage { get; set; } = 60f;
public float PotionCooldown { get; set; } = 30f;
public int ResurrectionCharges { get; set; } = 3;
}
public class HealthDamageMultiplier
{
public float HealthMultiplier { get; set; } = 1;
public float DamageMultiplier { get; set; } = 1;
}
public class MonsterConfig
{
public float AttacksPerSecond { get; set; } = 1.2f;
public float HealthMultiplier { get; set; } = 1;
public float HealthBonusMultiplier { get; set; } = 1;
public float DamageMultiplier { get; set; } = 1;
/// <summary>
/// Attack target range
/// </summary>
public float LookupRange { get; set; } = 80f;
/// <summary>
/// Total health bonus multiplier that can be applied to a monster
/// </summary>
public float HealthBonusMultiplierCap { get; set; } = 1.025f;
}
public class QuestConfig
{
public bool AutoSave { get; set; } = false;
public bool UnlockAllWaypoints { get; set; } = false;
}
public class PlayerMultiplierConfig
{
public ParagonConfig<float> Strength { get; set; } = new(1f);
public ParagonConfig<float> Dexterity { get; set; } = new(1f);
public ParagonConfig<float> Intelligence { get; set; } = new(1f);
public ParagonConfig<float> Vitality { get; set; } = new(1f);
}
public class PlayerConfig
{
public PlayerMultiplierConfig Multipliers = new();
}
public class ItemsConfig
{
public UnidentifiedDrop UnidentifiedDropChances { get; set; } = new();
}
public class UnidentifiedDrop
{
public float HighQuality { get; set; } = 30f;
public float NormalQuality { get; set; } = 5f;
}
public class MinimapConfig
{
public bool ForceVisibility { get; set; } = false;
}
public class NephalemRiftConfig
{
public float ProgressMultiplier { get; set; } = 1f;
public bool AutoFinish { get; set; } = false;
public int AutoFinishThreshold { get; set; } = 2;
public float OrbsChance { get; set; } = 0f;
}
public class GameModsConfig
{
public RateConfig Rate { get; set; } = new();
public HealthConfig Health { get; set; } = new();
public MonsterConfig Monster { get; set; } = new();
public HealthDamageMultiplier Boss { get; set; } = new();
public QuestConfig Quest { get; set; } = new();
public PlayerConfig Player { get; set; } = new();
public ItemsConfig Items { get; set; } = new();
public MinimapConfig Minimap { get; set; } = new();
public NephalemRiftConfig NephalemRift { get; set; } = new();
private static readonly Logger Logger = LogManager.CreateLogger();
public GameModsConfig() {}
static GameModsConfig()
{
CreateInstance();
}
public static void ReloadSettings()
{
CreateInstance();
}
private static readonly object InstanceCreationLock = new();
public static GameModsConfig Instance { get; private set; }
private static void CreateInstance()
{
lock (InstanceCreationLock)
{
if (!File.Exists("config.mods.json"))
{
Instance = CreateDefaultFile();
}
else
{
var content = File.ReadAllText("config.mods.json");
if (content.TryFromJson(out GameModsConfig config, out Exception ex))
{
Logger.Success("Game mods loaded successfully!");
var @new = config.ToJson(Formatting.Indented);
File.WriteAllText(@"config.mods.json", @new);
Instance = config;
return;
}
Logger.Fatal("An error occurred whilst loading $[white on red]$config.mods.json$[/]$ file. Please verify if the file is correct. Delete the file and try again.");
Program.Shutdown(ex);
}
}
}
private static GameModsConfig CreateDefaultFile()
{
var migration = GameServerConfig.Instance;
File.WriteAllText("config.mods.json", new GameModsConfig().ToJson());
Logger.Success("Game mods file created successfully!");
return new GameModsConfig();
}
}
public static class JsonExtensions
{
private const bool Indented = true;
public static string ToJson(this object obj, Formatting? formatting = null)
{
return JsonConvert.SerializeObject(obj, formatting ?? (Indented ? Formatting.Indented : Formatting.None));
}
public static bool TryFromJson<T>(this string obj, out T value)
where T: class, new()
{
try
{
value = obj.FromJson<T>();
return true;
}
catch (Exception ex)
{
value = default;
return false;
}
}
public static bool TryFromJson<T>(this string obj, out T value, out Exception exception)
where T: class, new()
{
try
{
value = obj.FromJson<T>();
exception = null;
return true;
}
catch (Exception ex)
{
value = default;
exception = ex;
return false;
}
}
public static T FromJson<T>(this string obj)
where T: class, new()
{
return JsonConvert.DeserializeObject<T>(obj);
}
public static dynamic FromJsonDynamic(this string obj)
{
return obj.FromJson<ExpandoObject>();
}
}

View File

@ -71,6 +71,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Rate of experience gain.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float RateExp
{
get => GetFloat(nameof(RateExp), 1);
@ -80,6 +81,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Rate of gold gain.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float RateMoney
{
get => GetFloat(nameof(RateMoney), 1);
@ -89,12 +91,14 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Rate of item drop.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float RateDrop
{
get => GetFloat(nameof(RateDrop), 1);
set => Set(nameof(RateDrop), value);
}
[Obsolete("Use GameModsConfig instead.")]
public float RateChangeDrop
{
get => GetFloat(nameof(RateChangeDrop), 1);
@ -104,6 +108,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Rate of monster's HP.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float RateMonsterHP
{
get => GetFloat(nameof(RateMonsterHP), 1);
@ -113,6 +118,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Rate of monster's damage.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float RateMonsterDMG
{
get => GetFloat(nameof(RateMonsterDMG), 1);
@ -122,6 +128,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Percentage that a unique, legendary, set or special item created is unidentified
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float ChanceHighQualityUnidentified
{
get => GetFloat(nameof(ChanceHighQualityUnidentified), 30f);
@ -131,6 +138,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Percentage that a normal item created is unidentified
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float ChanceNormalUnidentified
{
get => GetFloat(nameof(ChanceNormalUnidentified), 5f);
@ -140,6 +148,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Resurrection charges on changing worlds
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public int ResurrectionCharges
{
get => GetInt(nameof(ResurrectionCharges), 3);
@ -149,6 +158,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Boss Health Multiplier
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float BossHealthMultiplier
{
get => GetFloat(nameof(BossHealthMultiplier), 6f);
@ -158,6 +168,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Boss Damage Multiplier
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float BossDamageMultiplier
{
get => GetFloat(nameof(BossDamageMultiplier), 3f);
@ -167,6 +178,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Whether to bypass the quest's settings of "Saveable" to TRUE (unless in OpenWorld)
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public bool AutoSaveQuests
{
get => GetBoolean(nameof(AutoSaveQuests), false);
@ -176,6 +188,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Progress gained when killing a monster in Nephalem Rifts
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float NephalemRiftProgressMultiplier
{
get => GetFloat(nameof(NephalemRiftProgressMultiplier), 1f);
@ -185,6 +198,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// How much a health potion heals in percentage
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float HealthPotionRestorePercentage
{
get => GetFloat(nameof(HealthPotionRestorePercentage), 60f);
@ -194,6 +208,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Cooldown (in seconds) to use a health potion again.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float HealthPotionCooldown
{
get => GetFloat(nameof(HealthPotionCooldown), 30f);
@ -203,6 +218,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Unlocks all waypoints in the campaign.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public bool UnlockAllWaypoints
{
get => GetBoolean(nameof(UnlockAllWaypoints), false);
@ -212,6 +228,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Strength multiplier when you're not a paragon.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float StrengthMultiplier
{
get => GetFloat(nameof(StrengthMultiplier), 1f);
@ -221,6 +238,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Strength multiplier when you're a paragon.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float StrengthParagonMultiplier
{
get => GetFloat(nameof(StrengthParagonMultiplier), 1f);
@ -230,6 +248,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Dexterity multiplier when you're not a paragon.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float DexterityMultiplier
{
get => GetFloat(nameof(DexterityMultiplier), 1f);
@ -239,6 +258,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Dexterity multiplier when you're a paragon.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float DexterityParagonMultiplier
{
get => GetFloat(nameof(DexterityParagonMultiplier), 1f);
@ -248,6 +268,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Intelligence multiplier when you're not a paragon.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float IntelligenceMultiplier
{
get => GetFloat(nameof(IntelligenceMultiplier), 1f);
@ -257,6 +278,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Intelligence multiplier when you're a paragon.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float IntelligenceParagonMultiplier
{
get => GetFloat(nameof(IntelligenceParagonMultiplier), 1f);
@ -266,6 +288,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Vitality multiplier when you're not a paragon.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float VitalityMultiplier
{
get => GetFloat(nameof(VitalityMultiplier), 1f);
@ -275,6 +298,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Vitality multiplier when you're a paragon.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float VitalityParagonMultiplier
{
get => GetFloat(nameof(VitalityParagonMultiplier), 1f);
@ -284,6 +308,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Auto finishes nephalem rift when there's <see cref="NephalemRiftAutoFinishThreshold"></see> or less monsters left.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public bool NephalemRiftAutoFinish
{
get => GetBoolean(nameof(NephalemRiftAutoFinish), false);
@ -293,6 +318,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// If <see cref="NephalemRiftAutoFinish"></see> is enabled, this is the threshold.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public int NephalemRiftAutoFinishThreshold
{
get => GetInt(nameof(NephalemRiftAutoFinishThreshold), 2);
@ -302,6 +328,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Nephalem Rifts chance of spawning a orb.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public float NephalemRiftOrbsChance
{
get => GetFloat(nameof(NephalemRiftOrbsChance), 0f);
@ -311,6 +338,7 @@ namespace DiIiS_NA.GameServer
/// <summary>
/// Forces the game to reveal all the map.
/// </summary>
[Obsolete("Use GameModsConfig instead.")]
public bool ForceMinimapVisibility
{
get => GetBoolean(nameof(ForceMinimapVisibility), false);
@ -318,6 +346,7 @@ namespace DiIiS_NA.GameServer
}
#endregion
public static GameServerConfig Instance { get; } = new();
private GameServerConfig() : base("Game-Server")

View File

@ -0,0 +1,16 @@
public class ParagonConfig<T>
{
public T Normal { get; set; }
public T Paragon { get; set; }
public ParagonConfig() {}
public ParagonConfig(T defaultValue) : this(defaultValue, defaultValue)
{
}
public ParagonConfig(T normal, T paragon)
{
Normal = normal;
Paragon = paragon;
}
}

View File

@ -33,8 +33,10 @@ using System.Security.Permissions;
using System.Threading;
using System.Threading.Tasks;
using DiIiS_NA.Core.Extensions;
using DiIiS_NA.D3_GameServer;
using Spectre.Console;
using Environment = System.Environment;
using FluentNHibernate.Utils;
namespace DiIiS_NA
{
@ -47,12 +49,12 @@ namespace DiIiS_NA
}
class Program
{
private static readonly Logger Logger = LogManager.CreateLogger("BZ.Net");
private static readonly Logger Logger = LogManager.CreateLogger("Blizzless");
public static readonly DateTime StartupTime = DateTime.Now;
public static BattleBackend BattleBackend { get; set; }
public bool GameServersAvailable = true;
public const int MaxLevel = 70;
public const int MAX_LEVEL = 70;
public static GameServer.ClientSystem.GameServer GameServer;
public static Watchdog Watchdog;
@ -65,42 +67,54 @@ namespace DiIiS_NA
public static string RestServerIp = RestConfig.Instance.IP;
public static string PublicGameServerIp = DiIiS_NA.GameServer.NATConfig.Instance.PublicIP;
public static int Build => 30;
public static int Stage => 2;
public const int BUILD = 30;
public const int STAGE = 3;
public static TypeBuildEnum TypeBuild => TypeBuildEnum.Beta;
private static bool DiabloCoreEnabled = DiIiS_NA.GameServer.GameServerConfig.Instance.CoreActive;
private static bool _diabloCoreEnabled = DiIiS_NA.GameServer.GameServerConfig.Instance.CoreActive;
static async Task StartAsync()
private static readonly CancellationTokenSource CancellationTokenSource = new();
public static readonly CancellationToken Token = CancellationTokenSource.Token;
public static void Cancel() => CancellationTokenSource.Cancel();
public static void CancelAfter(TimeSpan span) => CancellationTokenSource.CancelAfter(span);
public static bool IsCancellationRequested() => CancellationTokenSource.IsCancellationRequested;
public void MergeCancellationWith(params CancellationToken[] tokens) =>
CancellationTokenSource.CreateLinkedTokenSource(tokens);
static void WriteBanner()
{
void RightTextRule(string text, string ruleStyle) => AnsiConsole.Write(new Rule(text).RuleStyle(ruleStyle));
string Url(string url) => $"[link={url}]{url}[/]";
RightTextRule("[dodgerblue1]Blizz[/][deepskyblue2]less[/]", "steelblue1");
RightTextRule($"[dodgerblue3]Build [/][deepskyblue3]{BUILD}[/]", "steelblue1_1");
RightTextRule($"[dodgerblue3]Stage [/][deepskyblue3]{STAGE}[/]", "steelblue1_1");
RightTextRule($"[deepskyblue3]{TypeBuild}[/]", "steelblue1_1");
RightTextRule($"Diablo III [red]RoS 2.7.4.84161[/] - {Url("https://github.com/blizzless/blizzless-diiis")}",
"red");
AnsiConsole.MarkupLine("");
AnsiConsole.MarkupLine("");
}
static async Task StartAsync(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
DbProviderFactories.RegisterFactory("Npgsql", NpgsqlFactory.Instance);
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
string name = $"Blizzless: Build {Build}, Stage: {Stage} - {TypeBuild}";
string name = $"Blizzless: Build {BUILD}, Stage: {STAGE} - {TypeBuild}";
SetTitle(name);
Maximize();
AnsiConsole.Write(new Rule("[dodgerblue1]Blizz[/][deepskyblue2]less[/]").RuleStyle("steelblue1"));
AnsiConsole.Write(new Rule($"[dodgerblue3]Build [/][deepskyblue3]{Build}[/]").RightJustified()
.RuleStyle("steelblue1_1"));
AnsiConsole.Write(new Rule($"[dodgerblue3]Stage [/][deepskyblue3]{Stage}[/]").RightJustified()
.RuleStyle("steelblue1_1"));
AnsiConsole.Write(new Rule($"[deepskyblue3]{TypeBuild}[/]").RightJustified().RuleStyle("steelblue1_1"));
AnsiConsole.Write(
new Rule(
$"Diablo III [red]RoS 2.7.4.84161[/] - [link=https://github.com/blizzless/blizzless-diiis]https://github.com/blizzless/blizzless-diiis[/]")
.RuleStyle("red"));
AnsiConsole.MarkupLine("");
Console.WriteLine();
if (LogConfig.Instance.Targets.Any(x => x.MaximizeWhenEnabled && x.Enabled))
Maximize();
WriteBanner();
InitLoggers();
#if DEBUG
DiabloCoreEnabled = true;
_diabloCoreEnabled = true;
Logger.Info("Forcing Diablo III Core to be $[green]$enabled$[/]$ on debug mode.");
#else
if (!_diabloCoreEnabled)
Logger.Warn("Diablo III Core is $[red]$disabled$[/]$.");
#endif
var mod = GameModsConfig.Instance;
#pragma warning disable CS4014
Task.Run(async () =>
#pragma warning restore CS4014
@ -122,6 +136,9 @@ namespace DiIiS_NA
$"Memory: {totalMemory:0.000} GB | " +
$"CPU Time: {cpuTime.ToSmallText()} | " +
$"Uptime: {uptime.ToSmallText()}";
if (IsCancellationRequested())
text = "SHUTTING DOWN: " + text;
if (SetTitle(text))
await Task.Delay(1000);
else
@ -192,7 +209,7 @@ namespace DiIiS_NA
GuildManager.PreLoadGuilds();
Logger.Info("Loading Diablo III - Core...");
if (DiabloCoreEnabled)
if (_diabloCoreEnabled)
{
if (!MPQStorage.Initialized)
{
@ -225,7 +242,7 @@ namespace DiIiS_NA
BattleBackend = new BattleBackend(loginConfig.BindIP, loginConfig.WebPort);
//Diablo 3 Game-Server
if (DiabloCoreEnabled)
if (_diabloCoreEnabled)
StartGameServer();
else Logger.Fatal("Game server is disabled in the configs.");
@ -238,28 +255,36 @@ namespace DiIiS_NA
IChannel boundChannel = await serverBootstrap.BindAsync(loginConfig.Port);
Logger.Info(
"$[bold red3_1]$Tip:$[/]$ graceful shutdown with $[red3_1]$CTRL+C$[/]$ or $[red3_1]$!q[uit]$[/]$ or $[red3_1]$!exit$[/]$.");
Logger.Info("$[bold red3_1]$" +
"Tip:$[/]$ SNO breakdown with $[red3_1]$!sno$[/]$ $[red3_1]$<fullSnoBreakdown(true:false)>$[/]$.");
while (true)
Logger.Info("$[bold deeppink4]$Gracefully$[/]$ shutdown with $[red3_1]$CTRL+C$[/]$ or $[deeppink4]$!q[uit]$[/]$.");
Logger.Info("{0}", IsCancellationRequested());
while (!IsCancellationRequested())
{
var line = Console.ReadLine();
if (line is null or "!q" or "!quit" or "!exit")
if(line == null){
continue;
}
if (line == "!q" || line == "!quit" || line == "!exit")
{
Logger.Info("Break !quit");
break;
if (line is "!cls" or "!clear" or "cls" or "clear")
}
if (line == "!cls" || line == "!clear" || line == "cls" || line == "clear")
{
AnsiConsole.Clear();
AnsiConsole.Cursor.SetPosition(0, 0);
continue;
}
if (line.ToLower().StartsWith("!sno"))
if (line.StartsWith("!sno", StringComparison.OrdinalIgnoreCase))
{
if (IsTargetEnabled("ansi"))
Console.Clear();
MPQStorage.Data.SnoBreakdown(line.ToLower().Equals("!sno 1") ||
line.ToLower().Equals("!sno true"));
MPQStorage.Data.SnoBreakdown(
line.Equals("!sno 1", StringComparison.OrdinalIgnoreCase) ||
line.Equals("!sno true", StringComparison.OrdinalIgnoreCase)
);
continue;
}
@ -268,56 +293,76 @@ namespace DiIiS_NA
if (PlayerManager.OnlinePlayers.Count > 0)
{
Logger.Success("Gracefully shutting down...");
Logger.Info(
$"Server is shutting down in 1 minute, $[blue]${PlayerManager.OnlinePlayers.Count} players$[/]$ are still online.");
PlayerManager.SendWhisper("Server is shutting down in 1 minute.");
await Task.Delay(TimeSpan.FromMinutes(1));
}
Shutdown(delay: 25);
Shutdown();
}
catch (Exception e)
{
Shutdown(e, delay: 200);
Logger.Info(e.ToString());
Shutdown(e);
}
finally
{
Logger.Trace("Shutdown in progress !");
await Task.WhenAll(
boss.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
worker.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));
}
}
private static void Shutdown(Exception exception = null, int delay = 200)
private static bool _shuttingDown = false;
public static void Shutdown(Exception exception = null)
{
// if (!IsTargetEnabled("ansi"))
Logger.Trace("Shutdown here");
Logger.Trace("Stack trace at shutdown: " + Environment.StackTrace); // Log the stack trace
if (_shuttingDown) return;
_shuttingDown = true;
if (!IsCancellationRequested())
Cancel();
AnsiTarget.StopIfRunning(IsTargetEnabled("ansi"));
if (exception != null)
{
AnsiTarget.StopIfRunning();
if (exception != null)
AnsiConsole.WriteLine(
"An unhandled exception occured at initialization. Please report this to the developers.");
AnsiConsole.WriteException(exception);
}
AnsiConsole.Progress().Start(ctx =>
{
var task = ctx.AddTask("[darkred_1]Shutting down[/] [white]in[/] [red underline]10 seconds[/]");
for (int i = 1; i < 11; i++)
{
AnsiConsole.WriteLine("An unhandled exception occured at initialization. Please report this to the developers.");
AnsiConsole.WriteException(exception);
}
AnsiConsole.Progress().Start(ctx =>
{
var task = ctx.AddTask("[red]Shutting down...[/]");
for (int i = 0; i < 100; i++)
task.Description = $"[darkred_1]Shutting down[/] [white]in[/] [red underline]{11 - i} seconds[/]";
for (int j = 0; j < 10; j++)
{
task.Increment(1);
Thread.Sleep(delay);
Thread.Sleep(100);
}
});
}
Environment.Exit(-1);
}
task.Description = $"[darkred_1]Shutting down now.[/]";
task.StopTask();
});
Environment.Exit(exception is null ? 0 : -1);
}
[HandleProcessCorruptedStateExceptions]
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
static async Task Main()
static async Task Main(string[] args)
{
args ??= Array.Empty<string>();
try
{
await StartAsync();
await StartAsync(args);
}
catch (Exception ex)
{
@ -326,7 +371,6 @@ namespace DiIiS_NA
}
[SecurityCritical]
[HandleProcessCorruptedStateExceptionsAttribute]
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
var ex = e.ExceptionObject as Exception;
@ -347,45 +391,25 @@ namespace DiIiS_NA
if (TargetsEnabled("ansi") > 1 || (IsTargetEnabled("console") && IsTargetEnabled("ansi")))
{
AnsiConsole.MarkupLine("[underline red on white]Fatal:[/] [red]You can't use both ansi and console targets at the same time, nor have more than one ansi target.[/]");
AnsiConsole.Progress().Start(ctx =>
{
var sd = ctx.AddTask("[red3_1]Shutting down[/]");
for (int i = 0; i < 100; i++)
{
sd.Increment(1);
Thread.Sleep(25);
}
});
Environment.Exit(-1);
AnsiConsole.MarkupLine("[underline red on white]Fatal:[/] [red]It is impossible to have both ANSI and Console targets activated concurrently.[/]");
Shutdown();
}
foreach (var targetConfig in LogConfig.Instance.Targets)
{
if (!targetConfig.Enabled)
continue;
LogTarget target = null;
switch (targetConfig.Target.ToLower())
LogTarget target = targetConfig.Target.ToLower() switch
{
case "ansi":
target = new AnsiTarget(
targetConfig.MinimumLevel,
targetConfig.MaximumLevel,
targetConfig.IncludeTimeStamps,
targetConfig.TimeStampFormat);
break;
case "console":
target = new ConsoleTarget(targetConfig.MinimumLevel, targetConfig.MaximumLevel,
targetConfig.IncludeTimeStamps,
targetConfig.TimeStampFormat);
break;
case "file":
target = new FileTarget(targetConfig.FileName, targetConfig.MinimumLevel,
targetConfig.MaximumLevel, targetConfig.IncludeTimeStamps,
targetConfig.TimeStampFormat,
targetConfig.ResetOnStartup);
break;
}
"ansi" => new AnsiTarget(targetConfig.MinimumLevel, targetConfig.MaximumLevel,
targetConfig.IncludeTimeStamps, targetConfig.TimeStampFormat),
"console" => new ConsoleTarget(targetConfig.MinimumLevel, targetConfig.MaximumLevel,
targetConfig.IncludeTimeStamps, targetConfig.TimeStampFormat),
"file" => new FileTarget(targetConfig.FileName, targetConfig.MinimumLevel,
targetConfig.MaximumLevel, targetConfig.IncludeTimeStamps, targetConfig.TimeStampFormat,
targetConfig.ResetOnStartup),
_ => null
};
if (target != null)
LogManager.AttachLogTarget(target);
@ -412,9 +436,10 @@ namespace DiIiS_NA
}
else
{
Logger.Info("Discord bot Disabled..");
Logger.Trace("Discord bot Disabled..");
}
DiIiS_NA.GameServer.GSSystem.GeneratorsSystem.SpawnGenerator.RegenerateDensity();
Logger.Trace("We are here first");
DiIiS_NA.GameServer.ClientSystem.GameServer.GSBackend = new GsBackend(LoginServerConfig.Instance.BindIP, LoginServerConfig.Instance.WebPort);
}
@ -432,7 +457,6 @@ namespace DiIiS_NA
}
[DllImport("kernel32.dll", ExactSpelling = true)]
static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
@ -444,7 +468,6 @@ namespace DiIiS_NA
const int RESTORE = 9;
private static void Maximize()
{
// if it's running on windows
try
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)

View File

@ -136,9 +136,7 @@ namespace DiIiS_NA.REST.Extensions
public static void Swap<T>(ref T left, ref T right)
{
T temp = left;
left = right;
right = temp;
(left, right) = (right, left);
}
#region Strings

View File

@ -88,7 +88,7 @@ namespace DiIiS_NA.REST
void HandleInfoRequest(HttpHeader request)
{
SendResponseHtml(HttpCode.OK, "Welcome to BlizzLess.Net" +
"\nBuild " + Program.Build +
"\nBuild " + Program.BUILD +
"\nSupport: 2.7.4");
}

View File

@ -45,16 +45,14 @@ namespace DiIiS_NA.REST
try
{
using (var socketEventargs = new SocketAsyncEventArgs())
{
socketEventargs.SetBuffer(_receiveBuffer, 0, _receiveBuffer.Length);
socketEventargs.Completed += (sender, args) => ReadHandlerInternal(args);
socketEventargs.SocketFlags = SocketFlags.None;
socketEventargs.RemoteEndPoint = _socket.RemoteEndPoint;
using var socketEventArgs = new SocketAsyncEventArgs();
socketEventArgs.SetBuffer(_receiveBuffer, 0, _receiveBuffer.Length);
socketEventArgs.Completed += (sender, args) => ReadHandlerInternal(args);
socketEventArgs.SocketFlags = SocketFlags.None;
socketEventArgs.RemoteEndPoint = _socket.RemoteEndPoint;
if (!_socket.ReceiveAsync(socketEventargs))
ReadHandlerInternal(socketEventargs);
}
if (!_socket.ReceiveAsync(socketEventArgs))
ReadHandlerInternal(socketEventArgs);
}
catch (Exception ex)
{

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