Skip to content

Commit

Permalink
feat: Implement 'game_item' module
Browse files Browse the repository at this point in the history
- Created a new 'game_item' module.
- Created RookiesRing.csx in the 'game_item' module.
- Moved Rookie's Ring settings from general server logic to specific
  RookiesRing.csx settings file.
- Implemented a new 'constant' and 'dynamic' mode for the Rookie's Ring.
- Added a new option to completely disable the Rookie's Ring.
  • Loading branch information
pacampbell committed Dec 23, 2024
1 parent 53be753 commit cd8243a
Show file tree
Hide file tree
Showing 14 changed files with 279 additions and 64 deletions.
23 changes: 9 additions & 14 deletions Arrowgene.Ddon.GameServer/Characters/ExpManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -699,30 +699,25 @@ public static uint TotalExpToLevelUpTo(uint level, GameMode gameMode)
return totalExp;
}

private double RookiesRingBonus()
{
return _GameSettings.RookiesRingBonus;
}

private uint RookiesRingMaxLevel()
{
return _GameSettings.RookiesRingMaxLevel;
}

private uint GetRookiesRingBonus(CharacterCommon characterCommon, uint baseExpAmount)
{
if (characterCommon.ActiveCharacterJobData.Lv > RookiesRingMaxLevel())
if (!_Server.GameLogicSettings.Get<bool>("RookiesRing", "EnableRookiesRing"))
{
return 0;
}

if (!characterCommon.Equipment.GetItems(EquipType.Performance).Exists(x => x?.ItemId == 11718))
if (!characterCommon.Equipment.GetItems(EquipType.Performance).Exists(x => x?.ItemId == (uint) ItemId.RookiesRing))
{
return 0;
}

double result = baseExpAmount * RookiesRingBonus();
return (uint)result;
var rookiesRingInterface = _Server.ScriptManager.GameItemModule.GetItemInterface(ItemId.RookiesRing);
if (rookiesRingInterface == null)
{
return baseExpAmount;
}

return (uint)(baseExpAmount * rookiesRingInterface.GetBonusMultiplier(_Server, characterCommon));
}

private uint GetCourseExpBonus(CharacterCommon characterCommon, uint baseExpAmount, RewardSource source, QuestType questType)
Expand Down
12 changes: 7 additions & 5 deletions Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using Arrowgene.Ddon.Server;
using Arrowgene.Ddon.Server.Network;
using Arrowgene.Ddon.Shared.Entity.PacketStructure;
using Arrowgene.Ddon.Shared.Entity.Structure;
using Arrowgene.Ddon.Shared.Model;
using Arrowgene.Ddon.Shared.Network;
using Arrowgene.Logging;
using Arrowgene.Ddon.GameServer.Dump;
using Arrowgene.Ddon.Shared.Entity.Structure;
using System.Collections.Generic;
using System.Linq;
using Arrowgene.Ddon.Shared.Model;
using Arrowgene.Ddon.GameServer.Characters;

namespace Arrowgene.Ddon.GameServer.Handler
{
Expand Down Expand Up @@ -51,6 +48,11 @@ public override void Handle(GameClient client, StructurePacket<C2SItemUseBagItem
_Server.JobManager.UnlockSecretAbility(client, client.Character, (SecretAbility) _Server.ItemManager.GetAbilityId(item.ItemId));
}

if (_Server.ScriptManager.GameItemModule.HasItem(item.ItemId))
{
_Server.ScriptManager.GameItemModule.GetItemInterface(item.ItemId)?.OnUse(_Server, client);
}

if (_Server.EpitaphRoadManager.TrialInProgress(client.Party))
{
_Server.EpitaphRoadManager.EvaluateItemUsed(client.Party, item.ItemId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class GameServerScriptManager : ScriptManager<GlobalVariables>
private DdonGameServer Server { get; }
private GlobalVariables Globals { get; }
public NpcExtendedFacilityModule NpcExtendedFacilityModule { get; private set; } = new NpcExtendedFacilityModule();
public GameItemModule GameItemModule { get; private set; } = new GameItemModule();

public GameServerScriptManager(DdonGameServer server) : base(server.AssetRepository.AssetsPath)
{
Expand All @@ -29,6 +30,7 @@ public GameServerScriptManager(DdonGameServer server) : base(server.AssetReposit

// Add modules to the list so the generic logic can iterate over all scripting modules
ScriptModules[NpcExtendedFacilityModule.ModuleRoot] = NpcExtendedFacilityModule;
ScriptModules[GameItemModule.ModuleRoot] = GameItemModule;
}

public override void Initialize()
Expand Down
21 changes: 21 additions & 0 deletions Arrowgene.Ddon.GameServer/Scripting/Interfaces/IGameItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Arrowgene.Ddon.Shared.Model;
using System.Collections.Generic;

namespace Arrowgene.Ddon.GameServer.Scripting.Interfaces
{
public abstract class IGameItem
{
public abstract ItemId ItemId { get; }

/**
* @brief Called when an item is used by a player.
*/
public abstract void OnUse(DdonGameServer server, GameClient client);

/**
* Called for items which have an impact to the player or pawn when they are
* equipped when completing certain actions such as killing enemies or completing quests.
*/
public abstract double GetBonusMultiplier(DdonGameServer server, CharacterCommon characterCommon);
}
}
78 changes: 78 additions & 0 deletions Arrowgene.Ddon.GameServer/Scripting/Modules/GameItemModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using Arrowgene.Ddon.GameServer.Scripting.Interfaces;
using Arrowgene.Ddon.Shared;
using Arrowgene.Ddon.Shared.Model;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Scripting;
using System.Collections.Generic;

namespace Arrowgene.Ddon.GameServer.Scripting
{
public class GameItemModule : ScriptModule
{
public override string ModuleRoot => "game_items";
public override string Filter => "*.csx";
public override bool ScanSubdirectories => true;
public override bool EnableHotLoad => true;

private Dictionary<ItemId, IGameItem> Items { get; set; }

public bool HasItem(ItemId itemId)
{
return Items.ContainsKey(itemId);
}

public bool HasItem(uint itemId)
{
return HasItem((ItemId)itemId);
}

public IGameItem? GetItemInterface(ItemId itemId)
{
if (!Items.ContainsKey(itemId))
{
return null;
}
return Items[itemId];
}

public IGameItem? GetItemInterface(uint itemId)
{
return GetItemInterface((ItemId)itemId);
}

public GameItemModule()
{
Items = new Dictionary<ItemId, IGameItem>();
}

public override ScriptOptions Options()
{
return ScriptOptions.Default
.AddReferences(MetadataReference.CreateFromFile(typeof(DdonGameServer).Assembly.Location))
.AddReferences(MetadataReference.CreateFromFile(typeof(AssetRepository).Assembly.Location))
.AddImports("System", "System.Collections", "System.Collections.Generic")
.AddImports("Arrowgene.Ddon.Shared")
.AddImports("Arrowgene.Ddon.Shared.Model")
.AddImports("Arrowgene.Ddon.GameServer")
.AddImports("Arrowgene.Ddon.GameServer.Characters")
.AddImports("Arrowgene.Ddon.GameServer.Scripting")
.AddImports("Arrowgene.Ddon.GameServer.Scripting.Interfaces")
.AddImports("Arrowgene.Ddon.Shared.Entity.PacketStructure")
.AddImports("Arrowgene.Ddon.Shared.Entity.Structure")
.AddImports("Arrowgene.Ddon.Shared.Model.Quest");
}

public override bool EvaluateResult(string path, ScriptState<object> result)
{
if (result == null)
{
return false;
}

IGameItem item = (IGameItem)result.ReturnValue;
Items[item.ItemId] = item;

return true;
}
}
}
16 changes: 7 additions & 9 deletions Arrowgene.Ddon.Server/Scripting/ScriptManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
using Arrowgene.Logging;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Scripting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using static Arrowgene.Ddon.Server.ServerScriptManager;

Expand Down Expand Up @@ -34,19 +36,20 @@ protected void Initialize(T globalVariables)
SetupFileWatchers();
}

protected void CompileScript(ScriptModule module, string path)
protected async void CompileScript(ScriptModule module, string path)
{
try
{
Logger.Info(path);

var code = Util.ReadAllText(path);
var script = CSharpScript.Create(
code: Util.ReadAllText(path),
code: code,
options: module.Options(),
globalsType: typeof(T)
);

var result = script.RunAsync(GlobalVariables).Result;
var result = await script.RunAsync(GlobalVariables);
if (!module.EvaluateResult(path, result))
{
Logger.Error($"Failed to evaluate the result of executing '{path}'");
Expand All @@ -66,13 +69,8 @@ protected void CompileScripts()
var path = $"{ScriptsRoot}\\{module.ModuleRoot}";

Logger.Info($"Compiling scripts for module '{module.ModuleRoot}'");
foreach (var file in Directory.EnumerateFiles(path))
foreach (var file in Directory.GetFiles(path, "*.csx", SearchOption.AllDirectories))
{
if (Path.GetExtension(file) != ".csx")
{
continue;
}

module.Scripts.Add(file);
CompileScript(module, file);
}
Expand Down
29 changes: 5 additions & 24 deletions Arrowgene.Ddon.Server/Scripting/interfaces/GameLogicSetting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ private T GetSetting<T>(string key)
return SettingsData.Get<T>("GameLogicSettings", key);
}

public T Get<T>(string scriptName, string key)
{
return SettingsData.Get<T>(scriptName, key);
}

/// <summary>
/// Additional factor to change how long crafting a recipe will take to finish.
/// </summary>
Expand All @@ -39,30 +44,6 @@ public double AdditionalCostPerformanceFactor
}
}

/// <summary>
/// Sets the maximim level that the exp ring will reward a bonus.
/// </summary>
public uint RookiesRingMaxLevel
{
get
{
return GetSetting<uint>("RookiesRingMaxLevel");
}
}

/// <summary>
/// The multiplier applied to the bonus amount of exp rewarded.
/// Must be a non-negtive value. If it is less than 0.0, a default of 1.0
/// will be selected.
/// </summary>
public double RookiesRingBonus
{
get
{
return GetSetting<double>("RookiesRingBonus");
}
}

/// <summary>
/// Controls whether to pass lobby context packets on demand or only on entry to the server.
/// True = Server entry only. Lower packet load, but also causes invisible people in lobbies.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Game Items

This module handles the implementation of items which requires the server to be involved.

- When creating a new `game_item`, create a `csx` file which matches the name of the item.
- If an item has many settings or complex settings, create a new settings file for the item
under `settings/game_items`, named the same as the `Item.csx`
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @brief Settings for this object can be found in
* <assets>/scripts/settings/game_items/RookiesRing.csx
*/
public class GameItem : IGameItem
{
private class RingMode
{
public const uint Constant = 0;
public const uint Dynamic = 1;
}

public override ItemId ItemId => ItemId.RookiesRing;

public GameItem()
{
}

public override void OnUse(DdonGameServer server, GameClient client)
{
// The rookies ring has no OnUse behavior
}

private double CalculateConstantBonus(DdonGameServer server, CharacterCommon characterCommon)
{
if (characterCommon.ActiveCharacterJobData.Lv > server.GameLogicSettings.Get<uint>("RookiesRing", "RookiesRingMaxLevel"))
{
return 0;
}

return server.GameLogicSettings.Get<double>("RookiesRing", "RookiesRingBonus");
}

private double CalculateDynamicBonus(DdonGameServer server, CharacterCommon characterCommon)
{
var dynamicBands = server.GameLogicSettings.Get<List<(uint MinLv, uint MaxLv, double ExpMultiplier)>>("RookiesRing", "DynamicExpBands");

var characterLv = characterCommon.ActiveCharacterJobData.Lv;

foreach (var band in dynamicBands)
{
if (characterLv >= band.MinLv && characterLv <= band.MaxLv)
{
return band.ExpMultiplier;
}
}
return 0;
}

public override double GetBonusMultiplier(DdonGameServer server, CharacterCommon characterCommon)
{
double bonus = 0;

var mode = server.GameLogicSettings.Get<uint>("RookiesRing", "RookiesRingMode");
switch (mode)
{
case RingMode.Dynamic:
bonus = CalculateDynamicBonus(server, characterCommon);
break;
default:
// Mode is either 0 or an invalid value, so treat it as zero
bonus = CalculateConstantBonus(server, characterCommon);
break;
}

return bonus;
}
}

return new GameItem();
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ double AdditionalProductionSpeedFactor = 1.0;
double AdditionalCostPerformanceFactor = 1.0;
double CraftConsumableProductionTimesMax = 10;

// Exp Ring Settings
bool EnableRookiesRing = false;
uint RookiesRingMaxLevel = 89;
double RookiesRingBonus = 1.0;

// EXP Penalty Settings

/**
Expand Down
2 changes: 2 additions & 0 deletions Arrowgene.Ddon.Shared/Files/Assets/scripts/settings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ This file attemps to establish some guidelines when defining new settings which
While implementing new GameLogicSettings, consider the possibility that the file can be hotlaoded after the server starts and code according to that assumption.
If the settings interacts with a feature which uses some caching mechanism, make sure to invalid all those caches when the file is reloaded.

If a certain category has many settings files, consider to create a subdirectory which contains all related or similar settings scripts.

## Naming Guidelines

#### Enable Variables
Expand Down
Loading

0 comments on commit cd8243a

Please sign in to comment.