8
.gitignore
vendored
@ -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
@ -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"]
|
||||
79
README.md
@ -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.
|
||||
@ -54,12 +62,17 @@ The currently supported version of the client: **2.7.4.84161**
|
||||
```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`
|
||||
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
|
||||
|
||||
Kanai’s Cube: fix legendary power extraction so it correctly consumes the required items.
|
||||
Enchant NPC: enable proper re-rolling of any item’s stats.
|
||||
Ramaladni’s 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 character’s 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 Death’s 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, Kanai’s Cube, Ramaladni’s 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)
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
@ -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
@ -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
|
After Width: | Height: | Size: 2.3 MiB |
BIN
pictures/d30.PNG
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
pictures/d32.PNG
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
pictures/d33.PNG
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
pictures/d34.PNG
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
pictures/d35.PNG
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
pictures/d36.PNG
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
pictures/d37.PNG
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
pictures/d38.PNG
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
@ -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);
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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)
|
||||
{
|
||||
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,11 +503,42 @@ namespace DiIiS_NA.LoginServer.Battle
|
||||
}
|
||||
public void SendMotd()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(LoginServerConfig.Instance.Motd) || !LoginServerConfig.Instance.MotdEnabled)
|
||||
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)
|
||||
{
|
||||
|
||||
@ -32,9 +32,7 @@
|
||||
{
|
||||
index2 = (byte)(key[index1] + _state[counter] + index2);
|
||||
// swap byte
|
||||
byte tmp = _state[counter];
|
||||
_state[counter] = _state[index2];
|
||||
_state[index2] = tmp;
|
||||
(_state[counter], _state[index2]) = (_state[index2], _state[counter]);
|
||||
index1 = (byte)((index1 + 1) % key.Length);
|
||||
}
|
||||
}
|
||||
@ -46,9 +44,7 @@
|
||||
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]);
|
||||
outputBuffer[outputOffset + counter] = (byte)(inputBuffer[inputOffset + counter] ^ _state[xorIndex]);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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")
|
||||
);
|
||||
|
||||
@ -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;
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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}] }}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
|
||||
11
src/DiIiS-NA/Core/Extensions/DataConversionExtensions.cs
Normal 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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,15 +58,18 @@ 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);
|
||||
if (clear)
|
||||
{
|
||||
AnsiConsole.Clear();
|
||||
AnsiConsole.Cursor.SetPosition(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logging keywords to beautify the output.
|
||||
@ -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,82 +123,58 @@ 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)
|
||||
{
|
||||
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);
|
||||
@ -193,40 +183,25 @@ public class AnsiTarget : LogTarget
|
||||
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());
|
||||
}
|
||||
}
|
||||
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() + "$[/]$";
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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) { }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,11 +116,36 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -35,8 +35,11 @@ namespace DiIiS_NA.Core.Storage.AccountDataBase
|
||||
if (_config == null)
|
||||
{
|
||||
_config = new Configuration();
|
||||
#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>();
|
||||
foreach (var prop in _config.Properties)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"))
|
||||
|
||||
@ -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!");
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
@ -23,7 +24,8 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
{
|
||||
public class MonsterBrain : Brain
|
||||
{
|
||||
private new readonly Logger Logger;
|
||||
private readonly Logger _logger;
|
||||
|
||||
// list of power SNOs that are defined for the monster
|
||||
public Dictionary<int, Cooldown> PresetPowers { get; private set; }
|
||||
|
||||
@ -36,9 +38,9 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
}
|
||||
|
||||
private bool _warnedNoPowers;
|
||||
private Actor _target { get; set; }
|
||||
private int _mpqPowerCount;
|
||||
private bool Feared = false;
|
||||
private Actor Target { get; set; }
|
||||
private readonly int _mpqPowerCount;
|
||||
private bool _feared = false;
|
||||
|
||||
public Actor AttackedBy = null;
|
||||
public TickTimer TimeoutAttacked = null;
|
||||
@ -48,30 +50,33 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
public MonsterBrain(Actor body)
|
||||
: base(body)
|
||||
{
|
||||
Logger = LogManager.CreateLogger(GetType().Name);
|
||||
_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");
|
||||
_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;
|
||||
|
||||
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))
|
||||
{
|
||||
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 });
|
||||
}
|
||||
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
|
||||
PresetPowers.Add(30592,
|
||||
new Cooldown { CooldownTimer = null, CooldownTime = 0f }); //hack for dummy mobs without powers
|
||||
}
|
||||
|
||||
public override void Think(int tickCounter)
|
||||
@ -88,7 +93,8 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
if (Body.Hidden)
|
||||
return;
|
||||
|
||||
if (CurrentAction != null && PriorityTarget != null && PriorityTarget.Attributes[GameAttributes.Is_Helper] == true)
|
||||
if (CurrentAction != null && PriorityTarget != null &&
|
||||
PriorityTarget.Attributes[GameAttributes.Is_Helper] == true)
|
||||
{
|
||||
PriorityTarget = null;
|
||||
CurrentAction.Cancel(tickCounter);
|
||||
@ -118,6 +124,7 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
CurrentAction.Cancel(tickCounter);
|
||||
CurrentAction = null;
|
||||
}
|
||||
|
||||
_powerDelay = null;
|
||||
|
||||
return;
|
||||
@ -125,14 +132,14 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
|
||||
if (Body.Attributes[GameAttributes.Feared])
|
||||
{
|
||||
if (!Feared || CurrentAction == null)
|
||||
{
|
||||
if (_feared && CurrentAction != null) return;
|
||||
if (CurrentAction != null)
|
||||
{
|
||||
CurrentAction.Cancel(tickCounter);
|
||||
CurrentAction = null;
|
||||
}
|
||||
Feared = true;
|
||||
|
||||
_feared = true;
|
||||
CurrentAction = new MoveToPointWithPathfindAction(
|
||||
Body,
|
||||
PowerContext.RandomDirection(Body.Position, 3f, 8f)
|
||||
@ -140,35 +147,40 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
_feared = false;
|
||||
|
||||
Feared = false;
|
||||
|
||||
if (CurrentAction == null)
|
||||
{
|
||||
if (CurrentAction != null) return;
|
||||
_powerDelay ??= new SecondsTickTimer(Body.World.Game, 1.0f);
|
||||
if (AttackedBy != null || Body.GetObjectsInRange<Player>(50f).Count != 0)
|
||||
{
|
||||
if (_powerDelay.TimedOut)
|
||||
// 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>(60f)
|
||||
targets = Body.GetObjectsInRange<Monster>(GameModsConfig.Instance.Monster.LookupRange +10f)
|
||||
.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)
|
||||
// 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)
|
||||
@ -176,55 +188,74 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
.OrderBy((actor) => PowerMath.Distance2D(actor.Position, Body.Position))
|
||||
.ToArray();
|
||||
|
||||
// If there are no targets, return
|
||||
if (targets.Length == 0) return;
|
||||
|
||||
_target = targets.First();
|
||||
// Set the first found target as the target
|
||||
Target = targets.First();
|
||||
}
|
||||
else
|
||||
_target = PriorityTarget;
|
||||
// If there is a priority target, set it as the target
|
||||
Target = PriorityTarget;
|
||||
|
||||
int powerToUse = PickPowerToUse();
|
||||
if (powerToUse > 0)
|
||||
{
|
||||
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)
|
||||
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);
|
||||
Body.TranslateFacing(Target.Position, false);
|
||||
|
||||
CurrentAction = new PowerAction(Body, powerToUse, _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 monsterAffixSkill)
|
||||
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = monsterAffixSkill.CooldownTime };
|
||||
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 };
|
||||
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 };
|
||||
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}]");
|
||||
_logger.Trace(
|
||||
$"{GetType().Name} $[underline white]${nameof(MoveToPointAction)}$[/]$ to target $[white]${Target.ActorType}$[/]$ [{Target.Position}]");
|
||||
CurrentAction = new MoveToPointAction(
|
||||
Body, _target.Position
|
||||
Body, Target.Position
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Trace($"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{_target.ActorType}] {_target.SNO.ToString()}");
|
||||
_logger.Trace(
|
||||
$"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
|
||||
CurrentAction = new MoveToTargetWithPathfindAction(
|
||||
Body,
|
||||
_target,
|
||||
attackRange + _target.ActorData.Cylinder.Ax2,
|
||||
Target,
|
||||
attackRange + Target.ActorData.Cylinder.Ax2,
|
||||
powerToUse
|
||||
);
|
||||
}
|
||||
@ -236,29 +267,38 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
ActorSno._a1dun_leor_firewall2 => 223284,
|
||||
_ => powerToUse
|
||||
};
|
||||
CurrentAction = new PowerAction(Body, powerToUse, _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)
|
||||
PresetPowers[powerToUse] = new Cooldown { CooldownTimer = null, CooldownTime = (power as MonsterAffixSkill).CooldownTime };
|
||||
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 };
|
||||
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 };
|
||||
}
|
||||
}
|
||||
if (powerToUse is 96925 or 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)
|
||||
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)
|
||||
@ -266,7 +306,7 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
|
||||
if (PriorityTarget == null)
|
||||
{
|
||||
var targets = Body.GetActorsInRange(50f)
|
||||
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())
|
||||
@ -287,28 +327,33 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
if (targets.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var monsterActor in targets.Where(tar => _target == null))
|
||||
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;
|
||||
Target = brain.AttackedBy;
|
||||
}
|
||||
else
|
||||
{
|
||||
_target = targets.First();
|
||||
Target = targets.First();
|
||||
}
|
||||
|
||||
foreach (var tar in targets)
|
||||
if (tar is DesctructibleLootContainer && tar.SNO.IsDoorOrBarricade() && tar.SNO != ActorSno._trout_wagon_barricade)
|
||||
{ _target = tar; break; }
|
||||
if (tar is DesctructibleLootContainer && tar.SNO.IsDoorOrBarricade() &&
|
||||
tar.SNO != ActorSno._trout_wagon_barricade)
|
||||
{
|
||||
Target = tar;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
_target = PriorityTarget;
|
||||
Target = PriorityTarget;
|
||||
|
||||
int powerToUse = PickPowerToUse();
|
||||
if (powerToUse > 0)
|
||||
{
|
||||
PowerScript power = PowerLoader.CreateImplementationForPowerSNO(powerToUse);
|
||||
power.User = Body;
|
||||
if (_target == null)
|
||||
if (Target == null)
|
||||
{
|
||||
/*
|
||||
if (!this.Body.ActorSNO.Name.ToLower().Contains("woodwraith") &&
|
||||
@ -322,59 +367,77 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
//*/
|
||||
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)
|
||||
|
||||
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
|
||||
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(
|
||||
$"{GetType().Name} {nameof(PowerAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
|
||||
// Logger.Trace("PowerAction to target");
|
||||
CurrentAction = new PowerAction(Body, powerToUse, _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 };
|
||||
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 };
|
||||
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}]");
|
||||
_logger.Trace(
|
||||
$"{GetType().Name} {nameof(MoveToPointAction)} to target [{Target.Position}]");
|
||||
|
||||
CurrentAction = new MoveToPointAction(
|
||||
Body, _target.Position
|
||||
Body, Target.Position
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Trace($"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{_target.ActorType}] {_target.SNO.ToString()}");
|
||||
_logger.Trace(
|
||||
$"{GetType().Name} {nameof(MoveToTargetWithPathfindAction)} to target [{Target.ActorType}] {Target.SNO.ToString()}");
|
||||
|
||||
CurrentAction = new MoveToTargetWithPathfindAction(
|
||||
Body,
|
||||
//(
|
||||
_target,// + MovementHelpers.GetMovementPosition(
|
||||
Target, // + MovementHelpers.GetMovementPosition(
|
||||
//new Vector3D(0, 0, 0),
|
||||
//this.Body.WalkSpeed,
|
||||
//MovementHelpers.GetFacingAngle(_target.Position, this.Body.Position),
|
||||
//6
|
||||
//)
|
||||
//)
|
||||
attackRange + _target.ActorData.Cylinder.Ax2,
|
||||
attackRange + Target.ActorData.Cylinder.Ax2,
|
||||
powerToUse
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
@ -383,8 +446,9 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
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)
|
||||
|
||||
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);
|
||||
@ -400,33 +464,44 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
break;
|
||||
tryC++;
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
public void FastAttack(Actor target, int skillSNO)
|
||||
public void FastAttack(Actor target, int skillSno)
|
||||
{
|
||||
PowerScript power = PowerLoader.CreateImplementationForPowerSNO(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);
|
||||
_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) };
|
||||
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 (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 };
|
||||
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.");
|
||||
_logger.Warn(
|
||||
$"Monster $[red]$\"{Body.Name}\"$[/]$ has no usable powers. {_mpqPowerCount} are defined in mpq data.");
|
||||
_warnedNoPowers = true;
|
||||
return -1;
|
||||
}
|
||||
@ -435,7 +510,9 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
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();
|
||||
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;
|
||||
@ -448,28 +525,28 @@ namespace DiIiS_NA.GameServer.GSSystem.AISystem.Brains
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void AddPresetPower(int powerSNO)
|
||||
public void AddPresetPower(int powerSno)
|
||||
{
|
||||
if (PresetPowers.ContainsKey(powerSNO))
|
||||
if (PresetPowers.ContainsKey(powerSno))
|
||||
{
|
||||
Logger.Debug($"Monster $[red]$\"{Body.Name}\"$[/]$ already has power {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.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)
|
||||
public void RemovePresetPower(int powerSno)
|
||||
{
|
||||
if (PresetPowers.ContainsKey(powerSNO))
|
||||
if (PresetPowers.ContainsKey(powerSno))
|
||||
{
|
||||
PresetPowers.Remove(powerSNO);
|
||||
PresetPowers.Remove(powerSno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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))
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,8 +50,8 @@ 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 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;
|
||||
@ -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,12 +91,16 @@ 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()
|
||||
{
|
||||
// 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;
|
||||
@ -93,40 +108,169 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
||||
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];
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@ -187,15 +331,13 @@ namespace DiIiS_NA.GameServer.GSSystem.ActorSystem
|
||||
lock (_adjustLock)
|
||||
{
|
||||
int count = player.World.Game.Players.Count;
|
||||
if (count > 0 && _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;
|
||||
|
||||
|
||||
11
src/DiIiS-NA/D3-GameServer/GSSystem/GameSystem/ActEnum.cs
Normal 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
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,7 +36,7 @@ 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)
|
||||
@ -56,7 +57,8 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
|
||||
PlayerIndex = gameClient.Player.PlayerIndex,
|
||||
});
|
||||
else
|
||||
player.SendMessage(new PlayerIndexMessage(Opcodes.PlayerLeaveGameMessage) //PlayerLeaveGameMessage
|
||||
player.SendMessage(
|
||||
new PlayerIndexMessage(Opcodes.PlayerLeaveGameMessage) //PlayerLeaveGameMessage
|
||||
{
|
||||
PlayerIndex = gameClient.Player.PlayerIndex,
|
||||
});
|
||||
@ -73,7 +75,8 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
|
||||
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);
|
||||
Logger.Error("Can't remove player ({0}) from game with id: {1}", gameClient.Player.Toon.Name,
|
||||
gameId);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -82,7 +85,7 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
|
||||
|
||||
//TODO: Move this inside player OnLeave event
|
||||
var toon = p.Toon;
|
||||
toon.TimePlayed += (int)(DateTimeExtensions.ToUnixTime(DateTime.UtcNow) - toon.LoginTime);
|
||||
toon.TimePlayed += (int)(DateTime.UtcNow.ToUnixTime() - toon.LoginTime);
|
||||
toon.ExperienceNext = p.ExperienceNext;
|
||||
|
||||
ClientSystem.GameServer.GSBackend.PlayerLeft(gameId);
|
||||
@ -97,13 +100,22 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
|
||||
|
||||
if (gameClient.BnetClient != null)
|
||||
{
|
||||
gameClient.BnetClient.Account.GameAccount.ScreenStatus = D3.PartyMessage.ScreenStatus.CreateBuilder().SetScreen(1).SetStatus(0).Build();
|
||||
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()"); }
|
||||
try
|
||||
{
|
||||
ClientSystem.GameServer.GSBackend.UpdateClient(toon.GameAccount.PersistentID,
|
||||
toon.Level, 1);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warn("Exception on RemovePlayerFromGame()");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,7 +124,10 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
|
||||
Logger.Error("RemovePlayerFromGame() gameClient.Game is null!");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorException(ex, nameof(RemovePlayerFromGame));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,11 +6,12 @@ using DiIiS_NA.Core.Logging;
|
||||
|
||||
namespace DiIiS_NA.GameServer.GSSystem.GameSystem
|
||||
{
|
||||
public static class GameUpdateManager
|
||||
[Obsolete("This class is obsolete and will be removed in the future.")]
|
||||
public class GameUpdateManager
|
||||
{
|
||||
private static readonly Logger Logger = LogManager.CreateLogger("ThreadSystem");
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
@ -71,7 +71,11 @@ namespace DiIiS_NA.GameServer.GSSystem.GameSystem
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
catch { }
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var game in InactiveGames)
|
||||
Games.Remove(game);
|
||||
|
||||
InactiveGames.Clear();
|
||||
}
|
||||
|
||||
_tickWatch.Stop();
|
||||
|
||||
var compensation = (int)(100 - _tickWatch.ElapsedMilliseconds); // the compensation value we need to sleep in order to get consistent 100 ms Game.Update().
|
||||
|
||||
if (_tickWatch.ElapsedMilliseconds > 100)
|
||||
if (game.MissedTicks > 60)
|
||||
{
|
||||
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.Warn("Game.Update() is running too slow. GameId: {0}", game.GameId);
|
||||
game.MissedTicks = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var game in inactiveGames)
|
||||
{
|
||||
game.Working = false;
|
||||
Games.Remove(game);
|
||||
}
|
||||
|
||||
inactiveGames.Clear();
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
var compensation = (int)(100 - stopwatch.ElapsedMilliseconds); // the compensation value we need to sleep in order to get consistent 100 ms Game.Update().
|
||||
|
||||
if (stopwatch.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
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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++)
|
||||
{
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
@ -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();
|
||||
}));
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
@ -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);
|
||||
}
|
||||
@ -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,7 +4012,7 @@ 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);
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
@ -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)
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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);
|
||||
Logger.Warn("Leah not found in world.");
|
||||
}
|
||||
else
|
||||
foreach (var l in leah)
|
||||
{
|
||||
Logger.Warn($"Leah not found in world {tristramWorld.SNO.ToString()} - quest 72095/step 32");
|
||||
}
|
||||
l.Hidden = false;
|
||||
l.SetVisible(true);
|
||||
}
|
||||
ListenConversation(198617, new Advance());
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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];
|
||||
|
||||
230
src/DiIiS-NA/D3-GameServer/GameModsConfig.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
|
||||
16
src/DiIiS-NA/D3-GameServer/ParagonMod.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
if (LogConfig.Instance.Targets.Any(x => x.MaximizeWhenEnabled && x.Enabled))
|
||||
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();
|
||||
|
||||
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"))
|
||||
{
|
||||
AnsiTarget.StopIfRunning();
|
||||
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)
|
||||
{
|
||||
AnsiConsole.WriteLine("An unhandled exception occured at initialization. Please report this to the developers.");
|
||||
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++)
|
||||
var task = ctx.AddTask("[darkred_1]Shutting down[/] [white]in[/] [red underline]10 seconds[/]");
|
||||
for (int i = 1; i < 11; 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);
|
||||
}
|
||||
|
||||
[HandleProcessCorruptedStateExceptions]
|
||||
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
|
||||
static async Task Main()
|
||||
task.Description = $"[darkred_1]Shutting down now.[/]";
|
||||
task.StopTask();
|
||||
});
|
||||
|
||||
Environment.Exit(exception is null ? 0 : -1);
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||