Merge branch 'main' into feat-NATS
This commit is contained in:
commit
4ade957f62
11 changed files with 574 additions and 322 deletions
|
|
@ -1,108 +1,108 @@
|
||||||
using System;
|
using System;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Server.Tests
|
namespace Server.Tests;
|
||||||
|
|
||||||
|
[Collection("Sequential Tests")]
|
||||||
|
public class TimerTests : IClassFixture<ServerFixture>
|
||||||
{
|
{
|
||||||
[Collection("Sequential Tests")]
|
[Theory]
|
||||||
public class TimerTests : IClassFixture<ServerFixture>
|
[InlineData(0L, 8L)]
|
||||||
|
[InlineData(80L, 80L)]
|
||||||
|
[InlineData(65L, 72L)]
|
||||||
|
[InlineData(32767L, 32768L)]
|
||||||
|
[InlineData(32768L, 32768L)]
|
||||||
|
[InlineData(32833L, 32840L)]
|
||||||
|
[InlineData(134217729L, 134217736L)]
|
||||||
|
public void TestVariousTimes(long ticks, long expectedTicks)
|
||||||
{
|
{
|
||||||
[Theory]
|
var timerTicks = new TimerTicks();
|
||||||
[InlineData(0L, 8L)]
|
Timer.Init(timerTicks.Ticks);
|
||||||
[InlineData(80L, 80L)]
|
|
||||||
[InlineData(65L, 72L)]
|
Timer.StartTimer(TimeSpan.FromMilliseconds(ticks), action);
|
||||||
[InlineData(32767L, 32768L)]
|
|
||||||
[InlineData(32768L, 32768L)]
|
var tickCount = expectedTicks / 8;
|
||||||
[InlineData(32833L, 32840L)]
|
|
||||||
[InlineData(134217729L, 134217736L)]
|
for (int i = 1; i <= tickCount; i++)
|
||||||
public void TestVariousTimes(long ticks, long expectedTicks)
|
|
||||||
{
|
{
|
||||||
var timerTicks = new TimerTicks();
|
timerTicks.Ticks = i * 8;
|
||||||
void action()
|
|
||||||
{
|
|
||||||
Assert.Equal(expectedTicks, timerTicks.Ticks);
|
|
||||||
timerTicks.ExecutedCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer.Init(timerTicks.Ticks);
|
Timer.Slice(timerTicks.Ticks);
|
||||||
|
|
||||||
Timer.StartTimer(TimeSpan.FromMilliseconds(ticks), action);
|
|
||||||
|
|
||||||
var tickCount = expectedTicks / 8;
|
|
||||||
|
|
||||||
for (int i = 1; i <= tickCount; i++)
|
|
||||||
{
|
|
||||||
timerTicks.Ticks = i * 8;
|
|
||||||
|
|
||||||
Timer.Slice(timerTicks.Ticks);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(1, timerTicks.ExecutedCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
Assert.Equal(1, timerTicks.ExecutedCount);
|
||||||
[InlineData(1000L, 1000L, 30000L, 30000L, 2)]
|
return;
|
||||||
public void TestIntervals(long delay, long expectedDelayTicks, long interval, long expectedIntervalTicks, int count)
|
|
||||||
|
void action()
|
||||||
{
|
{
|
||||||
var timerTicks = new TimerTicks();
|
Assert.Equal(expectedTicks, timerTicks.Ticks);
|
||||||
|
timerTicks.ExecutedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer.Init(timerTicks.Ticks);
|
[Theory]
|
||||||
|
[InlineData(1000L, 1000L, 30000L, 30000L, 2)]
|
||||||
|
public void TestIntervals(long delay, long expectedDelayTicks, long interval, long expectedIntervalTicks, int count)
|
||||||
|
{
|
||||||
|
var timerTicks = new TimerTicks();
|
||||||
|
|
||||||
Timer.StartTimer(TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(interval), count, action);
|
Timer.Init(timerTicks.Ticks);
|
||||||
|
|
||||||
var tickCount = (expectedDelayTicks + (expectedIntervalTicks * count - 1)) / 8;
|
Timer.StartTimer(TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(interval), count, action);
|
||||||
|
|
||||||
for (int i = 1; i <= tickCount; i++)
|
var tickCount = (expectedDelayTicks + (expectedIntervalTicks * count - 1)) / 8;
|
||||||
{
|
|
||||||
timerTicks.Ticks = i * 8;
|
|
||||||
|
|
||||||
Timer.Slice(timerTicks.Ticks);
|
for (int i = 1; i <= tickCount; i++)
|
||||||
}
|
{
|
||||||
|
timerTicks.Ticks = i * 8;
|
||||||
|
|
||||||
Assert.Equal(count, timerTicks.ExecutedCount);
|
Timer.Slice(timerTicks.Ticks);
|
||||||
return;
|
|
||||||
|
|
||||||
void action()
|
|
||||||
{
|
|
||||||
timerTicks.ExpectedTicks += timerTicks.ExecutedCount++ == 0 ? expectedDelayTicks : expectedIntervalTicks;
|
|
||||||
Assert.Equal(timerTicks.ExpectedTicks, timerTicks.Ticks);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
Assert.Equal(count, timerTicks.ExecutedCount);
|
||||||
public void TestTimerStartedOnTick()
|
return;
|
||||||
|
|
||||||
|
void action()
|
||||||
{
|
{
|
||||||
var timerTicks = new TimerTicks();
|
timerTicks.ExpectedTicks += timerTicks.ExecutedCount++ == 0 ? expectedDelayTicks : expectedIntervalTicks;
|
||||||
|
Assert.Equal(timerTicks.ExpectedTicks, timerTicks.Ticks);
|
||||||
Timer.Init(timerTicks.Ticks);
|
|
||||||
|
|
||||||
var timer = new SelfRunningTimer(timerTicks);
|
|
||||||
timer.Start();
|
|
||||||
|
|
||||||
Timer.Slice(128);
|
|
||||||
Assert.Equal(1, timerTicks.ExecutedCount);
|
|
||||||
Timer.Slice(256);
|
|
||||||
Assert.Equal(2, timerTicks.ExecutedCount);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class TimerTicks
|
[Fact]
|
||||||
|
public void TestTimerStartedOnTick()
|
||||||
|
{
|
||||||
|
var timerTicks = new TimerTicks();
|
||||||
|
|
||||||
|
Timer.Init(timerTicks.Ticks);
|
||||||
|
|
||||||
|
var timer = new SelfRunningTimer(timerTicks);
|
||||||
|
timer.Start();
|
||||||
|
|
||||||
|
Timer.Slice(128);
|
||||||
|
Assert.Equal(1, timerTicks.ExecutedCount);
|
||||||
|
Timer.Slice(256);
|
||||||
|
Assert.Equal(2, timerTicks.ExecutedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TimerTicks
|
||||||
|
{
|
||||||
|
public long ExpectedTicks;
|
||||||
|
public long Ticks;
|
||||||
|
public int ExecutedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SelfRunningTimer : Timer
|
||||||
|
{
|
||||||
|
private readonly TimerTicks _timerTicks;
|
||||||
|
public SelfRunningTimer(TimerTicks ticks) : base(TimeSpan.FromMilliseconds(100)) => _timerTicks = ticks;
|
||||||
|
|
||||||
|
protected override void OnTick()
|
||||||
{
|
{
|
||||||
public long ExpectedTicks;
|
if (_timerTicks.ExecutedCount++ == 0)
|
||||||
public long Ticks;
|
|
||||||
public int ExecutedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SelfRunningTimer : Timer
|
|
||||||
{
|
|
||||||
private readonly TimerTicks _timerTicks;
|
|
||||||
public SelfRunningTimer(TimerTicks ticks) : base(TimeSpan.FromMilliseconds(100)) => _timerTicks = ticks;
|
|
||||||
|
|
||||||
protected override void OnTick()
|
|
||||||
{
|
{
|
||||||
if (_timerTicks.ExecutedCount++ == 0)
|
Delay = TimeSpan.FromMilliseconds(100);
|
||||||
{
|
Start();
|
||||||
Delay = TimeSpan.FromMilliseconds(100);
|
|
||||||
Start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
175
Projects/Server/Client/BwtDecompress.cs
Normal file
175
Projects/Server/Client/BwtDecompress.cs
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Server;
|
||||||
|
|
||||||
|
public static class BwtDecompress
|
||||||
|
{
|
||||||
|
public static byte[] Decompress(Stream stream, int length)
|
||||||
|
{
|
||||||
|
var firstChar = (byte)stream.ReadByte();
|
||||||
|
|
||||||
|
Span<ushort> table = GC.AllocateUninitializedArray<ushort>(256 * 256);
|
||||||
|
Span<byte> output = GC.AllocateUninitializedArray<byte>(length);
|
||||||
|
BuildTable(table, firstChar);
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while (stream.Position < stream.Length)
|
||||||
|
{
|
||||||
|
var currentValue = firstChar;
|
||||||
|
var value = table[currentValue];
|
||||||
|
if (currentValue > 0)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
table[currentValue] = table[currentValue - 1];
|
||||||
|
} while (--currentValue > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
table[0] = value;
|
||||||
|
|
||||||
|
output[i++] = (byte)value;
|
||||||
|
firstChar = (byte)stream.ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
return InternalDecompress(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildTable(Span<ushort> table, byte startValue)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
var firstByte = startValue;
|
||||||
|
byte secondByte = 0;
|
||||||
|
for (var i = 0; i < 256 * 256; i++)
|
||||||
|
{
|
||||||
|
var val = (ushort)(firstByte + (secondByte << 8));
|
||||||
|
table[index++] = val;
|
||||||
|
|
||||||
|
firstByte++;
|
||||||
|
if (firstByte == 0)
|
||||||
|
{
|
||||||
|
secondByte++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] InternalDecompress(Span<byte> input)
|
||||||
|
{
|
||||||
|
Span<byte> symbolTable = stackalloc byte[256];
|
||||||
|
Span<byte> frequency = stackalloc byte[256];
|
||||||
|
Span<int> partialInput = stackalloc int[256 * 3];
|
||||||
|
|
||||||
|
for (var i = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
symbolTable[i] = (byte)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryMarshal.Cast<byte, int>(input)[..256].CopyTo(partialInput);
|
||||||
|
|
||||||
|
var sum = 0;
|
||||||
|
for (var i = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
sum += partialInput[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonZeroCount = 256 - partialInput[..256].Count(0);
|
||||||
|
|
||||||
|
Frequency(partialInput, frequency);
|
||||||
|
|
||||||
|
for (int i = 0, m = 0; i < nonZeroCount; ++i)
|
||||||
|
{
|
||||||
|
var freq = frequency[i];
|
||||||
|
symbolTable[input[m + 1024]] = freq;
|
||||||
|
partialInput[freq + 256] = m + 1;
|
||||||
|
m += partialInput[freq];
|
||||||
|
partialInput[freq + 512] = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
var val = symbolTable[0];
|
||||||
|
var output = GC.AllocateUninitializedArray<byte>(sum);
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ref var firstValRef = ref partialInput[val + 256];
|
||||||
|
output[count] = val;
|
||||||
|
|
||||||
|
if (firstValRef >= partialInput[val + 512])
|
||||||
|
{
|
||||||
|
if (nonZeroCount-- > 0)
|
||||||
|
{
|
||||||
|
ShiftLeftSimd(symbolTable, nonZeroCount);
|
||||||
|
val = symbolTable[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var idx = input[firstValRef + 1024];
|
||||||
|
firstValRef++;
|
||||||
|
|
||||||
|
if (idx != 0)
|
||||||
|
{
|
||||||
|
ShiftLeftSimd(symbolTable, idx);
|
||||||
|
symbolTable[idx] = val;
|
||||||
|
val = symbolTable[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
} while (count < sum);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Frequency(Span<int> input, Span<byte> output)
|
||||||
|
{
|
||||||
|
Span<int> tmp = stackalloc int[256];
|
||||||
|
input[..256].CopyTo(tmp);
|
||||||
|
|
||||||
|
for (var i = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
uint value = 0;
|
||||||
|
byte index = 0;
|
||||||
|
|
||||||
|
for (var j = 0; j < 256; j++)
|
||||||
|
{
|
||||||
|
if (tmp[j] > value)
|
||||||
|
{
|
||||||
|
index = (byte)j;
|
||||||
|
value = (uint)tmp[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[i] = index;
|
||||||
|
tmp[index] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ShiftLeftSimd(Span<byte> input, int max)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
var vectorSize = Vector<byte>.Count;
|
||||||
|
|
||||||
|
while (i + vectorSize <= max)
|
||||||
|
{
|
||||||
|
var vector = new Vector<byte>(input[(i + 1)..]);
|
||||||
|
vector.CopyTo(input[i..]);
|
||||||
|
i += vectorSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < max)
|
||||||
|
{
|
||||||
|
input[i] = input[i + 1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
*************************************************************************/
|
*************************************************************************/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
@ -96,28 +97,46 @@ public static class Localization
|
||||||
if (File.Exists(file))
|
if (File.Exists(file))
|
||||||
{
|
{
|
||||||
using var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
using var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
using var bin = new BinaryReader(fs);
|
Span<byte> header = stackalloc byte[6];
|
||||||
|
fs.Read(header);
|
||||||
|
|
||||||
bin.ReadInt32();
|
byte[] data;
|
||||||
bin.ReadInt16();
|
BufferReader br;
|
||||||
|
if (BinaryPrimitives.ReadInt32LittleEndian(header) != 2 || BinaryPrimitives.ReadInt16LittleEndian(header[4..]) != 1)
|
||||||
|
{
|
||||||
|
// Skip header
|
||||||
|
fs.Position = 4;
|
||||||
|
data = BwtDecompress.Decompress(fs, (int)fs.Length - 4);
|
||||||
|
br = new BufferReader(data);
|
||||||
|
|
||||||
|
var header2 = br.ReadInt(); // Header 2
|
||||||
|
var header1 = br.ReadShort(); // Header 1
|
||||||
|
|
||||||
|
if (header2 != 2 || header1 != 1)
|
||||||
|
{
|
||||||
|
throw new Exception($"Invalid cliloc header in {file}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = GC.AllocateUninitializedArray<byte>((int)fs.Length - 6);
|
||||||
|
fs.Read(data);
|
||||||
|
br = new BufferReader(data);
|
||||||
|
}
|
||||||
|
|
||||||
byte[] buffer = null;
|
byte[] buffer = null;
|
||||||
while (bin.BaseStream.Length != bin.BaseStream.Position)
|
while (br.Position < data.Length)
|
||||||
{
|
{
|
||||||
var number = bin.ReadInt32();
|
var number = br.ReadInt();
|
||||||
var flag = bin.ReadByte(); // Original, Custom, Modified
|
var flag = br.ReadByte(); // Original, Custom, Modified
|
||||||
var length = bin.ReadInt16();
|
var length = br.ReadShort();
|
||||||
|
|
||||||
if (buffer == null || buffer.Length < length)
|
if (buffer == null || buffer.Length < length)
|
||||||
{
|
{
|
||||||
buffer = GC.AllocateUninitializedArray<byte>(length);
|
buffer = GC.AllocateUninitializedArray<byte>(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytesRead = bin.Read(buffer, 0, length);
|
br.Read(buffer.AsSpan(0, length));
|
||||||
if (bytesRead != length)
|
|
||||||
{
|
|
||||||
throw new Exception($"Could not read enough bytes from {file}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var text = Encoding.UTF8.GetString(buffer.AsSpan(0, length));
|
var text = Encoding.UTF8.GetString(buffer.AsSpan(0, length));
|
||||||
entries[number] = new LocalizationEntry(lang, number, text);
|
entries[number] = new LocalizationEntry(lang, number, text);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -353,6 +354,8 @@ public static class Core
|
||||||
|
|
||||||
public static void Setup(Assembly applicationAssembly, Process process)
|
public static void Setup(Assembly applicationAssembly, Process process)
|
||||||
{
|
{
|
||||||
|
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
Process = process;
|
Process = process;
|
||||||
ApplicationAssembly = applicationAssembly;
|
ApplicationAssembly = applicationAssembly;
|
||||||
Assembly = Assembly.GetAssembly(typeof(Core));
|
Assembly = Assembly.GetAssembly(typeof(Core));
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*************************************************************************
|
/*************************************************************************
|
||||||
* ModernUO *
|
* ModernUO *
|
||||||
* Copyright 2019-2023 - ModernUO Development Team *
|
* Copyright 2019-2024 - ModernUO Development Team *
|
||||||
* Email: hi@modernuo.com *
|
* Email: hi@modernuo.com *
|
||||||
* File: Timer.TimerWheel.cs *
|
* File: Timer.TimerWheel.cs *
|
||||||
* *
|
* *
|
||||||
|
|
@ -18,6 +18,7 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Server;
|
namespace Server;
|
||||||
|
|
||||||
|
|
@ -33,9 +34,9 @@ public partial class Timer
|
||||||
private const int _tickRate = 1 << _tickRatePowerOf2; // 8ms
|
private const int _tickRate = 1 << _tickRatePowerOf2; // 8ms
|
||||||
private const long _maxDuration = (long)_tickRate << (_ringSizePowerOf2 * _ringLayers - 1);
|
private const long _maxDuration = (long)_tickRate << (_ringSizePowerOf2 * _ringLayers - 1);
|
||||||
|
|
||||||
private static Timer[][] _rings = new Timer[_ringLayers][];
|
private static readonly Timer[][] _rings = new Timer[_ringLayers][];
|
||||||
private static int[] _ringIndexes = new int[_ringLayers];
|
private static readonly int[] _ringIndexes = new int[_ringLayers];
|
||||||
private static Timer[] _executingRings = new Timer[_ringLayers];
|
private static readonly Timer[] _executingRings = new Timer[_ringLayers];
|
||||||
|
|
||||||
private static long _lastTickTurned = -1;
|
private static long _lastTickTurned = -1;
|
||||||
|
|
||||||
|
|
@ -155,9 +156,7 @@ public partial class Timer
|
||||||
|
|
||||||
if (!finished)
|
if (!finished)
|
||||||
{
|
{
|
||||||
timer.Delay = timer.Interval;
|
AddTimer(timer, (long)timer.Interval.TotalMilliseconds);
|
||||||
timer.Next = DateTime.UtcNow + timer.Interval;
|
|
||||||
AddTimer(timer, (long)timer.Delay.TotalMilliseconds);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -168,10 +167,21 @@ public partial class Timer
|
||||||
timer.Index++;
|
timer.Index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static long RoundTicksToNextPowerOfTwo(long value)
|
||||||
|
{
|
||||||
|
if (value <= 0)
|
||||||
|
{
|
||||||
|
return _tickRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const long mask = _tickRate - 1;
|
||||||
|
return (value + mask) & ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddTimer(Timer timer, long delay)
|
private static void AddTimer(Timer timer, long delay)
|
||||||
{
|
{
|
||||||
var originalDelay = delay;
|
var actualDelay = delay;
|
||||||
delay = Math.Max(0, delay);
|
|
||||||
|
|
||||||
var resolutionPowerOf2 = _tickRatePowerOf2;
|
var resolutionPowerOf2 = _tickRatePowerOf2;
|
||||||
for (var i = 0; i < _ringLayers; i++)
|
for (var i = 0; i < _ringLayers; i++)
|
||||||
|
|
@ -205,7 +215,7 @@ public partial class Timer
|
||||||
logger.Error(
|
logger.Error(
|
||||||
$"Timer {{Timer}} has a duration of {{Duration}}ms, more than max capacity of {{MaxDuration}}ms.{Environment.NewLine}{{StackTrace}}",
|
$"Timer {{Timer}} has a duration of {{Duration}}ms, more than max capacity of {{MaxDuration}}ms.{Environment.NewLine}{{StackTrace}}",
|
||||||
timer.GetType(),
|
timer.GetType(),
|
||||||
originalDelay,
|
actualDelay,
|
||||||
_maxDuration,
|
_maxDuration,
|
||||||
new StackTrace()
|
new StackTrace()
|
||||||
);
|
);
|
||||||
|
|
@ -214,18 +224,19 @@ public partial class Timer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timer.Next = Core.Now + timer.Delay;
|
||||||
timer.Attach(_rings[i][slot]);
|
timer.Attach(_rings[i][slot]);
|
||||||
timer._remaining = remaining;
|
timer._remaining = remaining;
|
||||||
timer._ring = i;
|
timer._ring = i;
|
||||||
timer._slot = (int)slot;
|
timer._slot = (int)slot;
|
||||||
|
|
||||||
_rings[i][slot] = timer;
|
_rings[i][slot] = timer;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The remaining amount until we turn this ring
|
// The remaining amount until we turn this ring
|
||||||
delay -= resolution * (_ringSize - _ringIndexes[i]);
|
var offsetDelay = resolution * (_ringSize - _ringIndexes[i]);
|
||||||
|
delay -= offsetDelay;
|
||||||
resolutionPowerOf2 = nextResolutionPowerOf2;
|
resolutionPowerOf2 = nextResolutionPowerOf2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*************************************************************************
|
/*************************************************************************
|
||||||
* ModernUO *
|
* ModernUO *
|
||||||
* Copyright 2019-2023 - ModernUO Development Team *
|
* Copyright 2019-2024 - ModernUO Development Team *
|
||||||
* Email: hi@modernuo.com *
|
* Email: hi@modernuo.com *
|
||||||
* File: Timer.cs *
|
* File: Timer.cs *
|
||||||
* *
|
* *
|
||||||
|
|
@ -34,6 +34,8 @@ public partial class Timer
|
||||||
private long _remaining;
|
private long _remaining;
|
||||||
private Timer _nextTimer;
|
private Timer _nextTimer;
|
||||||
private Timer _prevTimer;
|
private Timer _prevTimer;
|
||||||
|
private TimeSpan _delay;
|
||||||
|
private TimeSpan _interval;
|
||||||
|
|
||||||
public Timer(TimeSpan delay) => Init(delay, TimeSpan.Zero, 1);
|
public Timer(TimeSpan delay) => Init(delay, TimeSpan.Zero, 1);
|
||||||
|
|
||||||
|
|
@ -43,14 +45,14 @@ public partial class Timer
|
||||||
|
|
||||||
protected void Init(TimeSpan delay, TimeSpan interval, int count)
|
protected void Init(TimeSpan delay, TimeSpan interval, int count)
|
||||||
{
|
{
|
||||||
Running = false;
|
|
||||||
Delay = delay;
|
Delay = delay;
|
||||||
Index = 0;
|
Next = DateTime.MinValue;
|
||||||
Interval = interval;
|
Interval = interval;
|
||||||
Count = count;
|
Count = count;
|
||||||
|
Running = false;
|
||||||
|
Index = 0;
|
||||||
_nextTimer = null;
|
_nextTimer = null;
|
||||||
_prevTimer = null;
|
_prevTimer = null;
|
||||||
Next = Core.Now + Delay;
|
|
||||||
_ring = -1;
|
_ring = -1;
|
||||||
_slot = -1;
|
_slot = -1;
|
||||||
}
|
}
|
||||||
|
|
@ -58,8 +60,19 @@ public partial class Timer
|
||||||
protected int Version { get; set; } // Used to determine if a timer was altered and we should abandon it.
|
protected int Version { get; set; } // Used to determine if a timer was altered and we should abandon it.
|
||||||
|
|
||||||
public DateTime Next { get; private set; }
|
public DateTime Next { get; private set; }
|
||||||
public TimeSpan Delay { get; set; }
|
|
||||||
public TimeSpan Interval { get; set; }
|
public TimeSpan Delay
|
||||||
|
{
|
||||||
|
get => _delay;
|
||||||
|
set => _delay = TimeSpan.FromMilliseconds(RoundTicksToNextPowerOfTwo((long)value.TotalMilliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan Interval
|
||||||
|
{
|
||||||
|
get => _interval;
|
||||||
|
set => _interval = TimeSpan.FromMilliseconds(RoundTicksToNextPowerOfTwo((long)value.TotalMilliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
public int Index { get; private set; }
|
public int Index { get; private set; }
|
||||||
public int Count { get; private set; }
|
public int Count { get; private set; }
|
||||||
public int RemainingCount => Count == 0 ? int.MaxValue : Count - Index;
|
public int RemainingCount => Count == 0 ? int.MaxValue : Count - Index;
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ public class HonorContext
|
||||||
Source.Mana += restore;
|
Source.Mana += restore;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VirtueSystem.GetVirtues(Source).Honor > targetFame)
|
if (VirtueSystem.GetVirtues(Source)?.Honor > targetFame)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +194,7 @@ public class HonorContext
|
||||||
// Initial honor gain is 100th of the monsters honor
|
// Initial honor gain is 100th of the monsters honor
|
||||||
var dGain = targetFame / 100.0 * (_honorDamage / _totalDamage);
|
var dGain = targetFame / 100.0 * (_honorDamage / _totalDamage);
|
||||||
|
|
||||||
if (_honorDamage == _totalDamage && _firstHit == FirstHit.Granted)
|
if (Math.Abs(_honorDamage - _totalDamage) < 0.01 && _firstHit == FirstHit.Granted)
|
||||||
{
|
{
|
||||||
dGain *= 1.5; // honor gain is increased a lot more if the combat was fully honorable
|
dGain *= 1.5; // honor gain is increased a lot more if the combat was fully honorable
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ public class JusticeVirtue
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddProtection(protectee, protector);
|
AddProtection(protector, protectee);
|
||||||
|
|
||||||
var args = $"{protector.Name}\t{protectee.Name}";
|
var args = $"{protector.Name}\t{protectee.Name}";
|
||||||
|
|
||||||
|
|
@ -267,67 +267,75 @@ public class JusticeVirtue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AcceptProtectorGump : Gump
|
public class AcceptProtectorGump : StaticGump<AcceptProtectorGump>
|
||||||
{
|
{
|
||||||
private readonly PlayerMobile _protectee;
|
|
||||||
private readonly PlayerMobile _protector;
|
private readonly PlayerMobile _protector;
|
||||||
|
private readonly PlayerMobile _protectee;
|
||||||
|
|
||||||
public AcceptProtectorGump(PlayerMobile protector, PlayerMobile protectee) : base(150, 50)
|
public AcceptProtectorGump(PlayerMobile protector, PlayerMobile protectee) : base(150, 50)
|
||||||
{
|
{
|
||||||
_protector = protector;
|
_protector = protector;
|
||||||
_protectee = protectee;
|
_protectee = protectee;
|
||||||
|
}
|
||||||
|
|
||||||
Closable = false;
|
protected override void BuildLayout(ref StaticGumpBuilder builder)
|
||||||
|
{
|
||||||
|
builder.SetNoClose();
|
||||||
|
|
||||||
AddPage(0);
|
builder.AddPage();
|
||||||
|
|
||||||
AddBackground(0, 0, 396, 218, 3600);
|
builder.AddBackground(0, 0, 396, 218, 3600);
|
||||||
|
|
||||||
AddImageTiled(15, 15, 365, 190, 2624);
|
builder.AddImageTiled(15, 15, 365, 190, 2624);
|
||||||
AddAlphaRegion(15, 15, 365, 190);
|
builder.AddAlphaRegion(15, 15, 365, 190);
|
||||||
|
|
||||||
// Another player is offering you their <a href="?ForceTopic88">protection</a>:
|
// Another player is offering you their <a href="?ForceTopic88">protection</a>:
|
||||||
AddHtmlLocalized(30, 20, 360, 25, 1049365, 0x7FFF);
|
builder.AddHtmlLocalized(30, 20, 360, 25, 1049365, 0x7FFF);
|
||||||
AddLabel(90, 55, 1153, protector.Name);
|
builder.AddLabelPlaceholder(90, 55, 1153, "protector");
|
||||||
|
|
||||||
AddImage(50, 45, 9005);
|
builder.AddImage(50, 45, 9005);
|
||||||
AddImageTiled(80, 80, 200, 1, 9107);
|
builder.AddImageTiled(80, 80, 200, 1, 9107);
|
||||||
AddImageTiled(95, 82, 200, 1, 9157);
|
builder.AddImageTiled(95, 82, 200, 1, 9157);
|
||||||
|
|
||||||
AddRadio(30, 110, 9727, 9730, true, 1);
|
builder.AddRadio(30, 110, 9727, 9730, true, 1);
|
||||||
AddHtmlLocalized(65, 115, 300, 25, 1049444, 0x7FFF); // Yes, I would like their protection.
|
builder.AddHtmlLocalized(65, 115, 300, 25, 1049444, 0x7FFF); // Yes, I would like their protection.
|
||||||
|
|
||||||
AddRadio(30, 145, 9727, 9730, false, 0);
|
builder.AddRadio(30, 145, 9727, 9730, false, 0);
|
||||||
AddHtmlLocalized(65, 148, 300, 25, 1049445, 0x7FFF); // No thanks, I can take care of myself.
|
builder.AddHtmlLocalized(65, 148, 300, 25, 1049445, 0x7FFF); // No thanks, I can take care of myself.
|
||||||
|
|
||||||
AddButton(160, 175, 247, 248, 2);
|
builder.AddButton(160, 175, 247, 248, 2);
|
||||||
|
|
||||||
AddImage(215, 0, 50581);
|
builder.AddImage(215, 0, 50581);
|
||||||
|
|
||||||
AddImageTiled(15, 14, 365, 1, 9107);
|
builder.AddImageTiled(15, 14, 365, 1, 9107);
|
||||||
AddImageTiled(380, 14, 1, 190, 9105);
|
builder.AddImageTiled(380, 14, 1, 190, 9105);
|
||||||
AddImageTiled(15, 205, 365, 1, 9107);
|
builder.AddImageTiled(15, 205, 365, 1, 9107);
|
||||||
AddImageTiled(15, 14, 1, 190, 9105);
|
builder.AddImageTiled(15, 14, 1, 190, 9105);
|
||||||
AddImageTiled(0, 0, 395, 1, 9157);
|
builder.AddImageTiled(0, 0, 395, 1, 9157);
|
||||||
AddImageTiled(394, 0, 1, 217, 9155);
|
builder.AddImageTiled(394, 0, 1, 217, 9155);
|
||||||
AddImageTiled(0, 216, 395, 1, 9157);
|
builder.AddImageTiled(0, 216, 395, 1, 9157);
|
||||||
AddImageTiled(0, 0, 1, 217, 9155);
|
builder.AddImageTiled(0, 0, 1, 217, 9155);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BuildStrings(ref GumpStringsBuilder builder)
|
||||||
|
{
|
||||||
|
builder.SetStringSlot("protector", _protector.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnResponse(NetState sender, in RelayInfo info)
|
public override void OnResponse(NetState sender, in RelayInfo info)
|
||||||
{
|
{
|
||||||
if (info.ButtonID == 2)
|
if (info.ButtonID != 2)
|
||||||
{
|
{
|
||||||
var okay = info.IsSwitched(1);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (okay)
|
if (info.IsSwitched(1)) // okay
|
||||||
{
|
{
|
||||||
JusticeVirtue.OnVirtueAccepted(_protector, _protectee);
|
JusticeVirtue.OnVirtueAccepted(_protector, _protectee);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JusticeVirtue.OnVirtueRejected(_protector, _protectee);
|
JusticeVirtue.OnVirtueRejected(_protector, _protectee);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,19 @@ public abstract partial class MonsterAbility
|
||||||
public virtual TimeSpan MinTriggerCooldown => TimeSpan.Zero;
|
public virtual TimeSpan MinTriggerCooldown => TimeSpan.Zero;
|
||||||
public virtual TimeSpan MaxTriggerCooldown => TimeSpan.Zero;
|
public virtual TimeSpan MaxTriggerCooldown => TimeSpan.Zero;
|
||||||
|
|
||||||
// To prevent reflect from harming the monster
|
|
||||||
public virtual bool CanTriggerAgainstSelf => false;
|
|
||||||
|
|
||||||
public bool WillTrigger(MonsterAbilityTrigger trigger) => (AbilityTrigger & trigger) != 0;
|
public bool WillTrigger(MonsterAbilityTrigger trigger) => (AbilityTrigger & trigger) != 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if ability is not on cooldown, and the change to trigger succeeds.
|
/// Returns true if ability is not on cooldown, and the chance to trigger succeeds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Boolean indicating the ability can trigger.</returns>
|
/// <returns>Boolean indicating the ability can trigger.</returns>
|
||||||
public virtual bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger)
|
public virtual bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger)
|
||||||
{
|
{
|
||||||
|
if (source is not { Alive: true, Deleted: false })
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (_nextTriggerTicks?.TryGetValue(source, out var nextTrigger) == true && nextTrigger - Core.TickCount > 0)
|
if (_nextTriggerTicks?.TryGetValue(source, out var nextTrigger) == true && nextTrigger - Core.TickCount > 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -54,11 +56,11 @@ public abstract partial class MonsterAbility
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Triggers the monster's ability. Override this and call `base.Trigger(source);` to make sure
|
/// Triggers the monster's ability. Override this and call `base.Trigger(source);` to make sure
|
||||||
/// the cooldown is tracked.
|
/// the cooldown is tracked.
|
||||||
|
/// Note: This can fire after a monster is killed/deleted (map is null)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target)
|
public virtual void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target)
|
||||||
{
|
{
|
||||||
if (!CanTriggerAgainstSelf && target == source ||
|
if (MinTriggerCooldown <= TimeSpan.Zero && MaxTriggerCooldown <= TimeSpan.Zero)
|
||||||
MinTriggerCooldown <= TimeSpan.Zero && MaxTriggerCooldown <= TimeSpan.Zero)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -612,217 +612,233 @@ namespace Server.Mobiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BarkeeperTitleGump : Gump
|
public class BarkeeperTitleGump : StaticGump<BarkeeperTitleGump>
|
||||||
{
|
{
|
||||||
private static readonly Entry[] m_Entries =
|
private static readonly Entry[] _entries =
|
||||||
{
|
{
|
||||||
new("Alchemist"),
|
new(1076083, "the alchemist"),
|
||||||
new("Animal Tamer"),
|
new(1077244, "the animal tamer"),
|
||||||
new("Apothecary"),
|
new(1078440, "the apothecary"),
|
||||||
new("Artist"),
|
new(1078441, "the artist"),
|
||||||
new("Baker", true),
|
new(1078442, "the baker", true),
|
||||||
new("Bard"),
|
new(1073298, "the bard"),
|
||||||
new("Barkeep", "the barkeeper", true),
|
new(1076102, "the barkeep", true),
|
||||||
new("Beggar"),
|
new(1078443, "the beggar"),
|
||||||
new("Blacksmith"),
|
new(1073297, "the blacksmith"),
|
||||||
new("Bounty Hunter"),
|
new(1078444, "the bounty hunter"),
|
||||||
new("Brigand"),
|
new(1078446, "the brigand"),
|
||||||
new("Butler"),
|
new(1078447, "the butler"),
|
||||||
new("Carpenter"),
|
new(1060774, "the carpenter"),
|
||||||
new("Chef", true),
|
new(1078448, "the chef", true),
|
||||||
new("Commander"),
|
new(1078449, "the commander"),
|
||||||
new("Curator"),
|
new(1078450, "the curator"),
|
||||||
new("Drunkard"),
|
new(1078451, "the drunkard"),
|
||||||
new("Farmer"),
|
new(1078452, "the farmer"),
|
||||||
new("Fisherman"),
|
new(1078453, "the fisherman"),
|
||||||
new("Gambler"),
|
new(1078454, "the gambler"),
|
||||||
new("Gypsy"),
|
new(1078455, "the gypsy"),
|
||||||
new("Herald"),
|
new(1075996, "the herald"),
|
||||||
new("Herbalist"),
|
new(1076107, "the herbalist"),
|
||||||
new("Hermit"),
|
new(1078465, "the hermit"),
|
||||||
new("Innkeeper", true),
|
new(1078466, "the innkeeper", true),
|
||||||
new("Jailor"),
|
new(1078467, "the jailor"),
|
||||||
new("Jester"),
|
new(1078468, "the jester"),
|
||||||
new("Librarian"),
|
new(1078469, "the librarian"),
|
||||||
new("Mage"),
|
new(1073292, "the mage"),
|
||||||
new("Mercenary"),
|
new(1078470, "the mercenary"),
|
||||||
new("Merchant"),
|
new(1060775, "the merchant"),
|
||||||
new("Messenger"),
|
new(1078472, "the messenger"),
|
||||||
new("Miner"),
|
new(1076093, "the miner"),
|
||||||
new("Monk"),
|
new(1078475, "the monk"),
|
||||||
new("Noble"),
|
new(1078476, "the noble"),
|
||||||
new("Paladin"),
|
new(1073290, "the paladin"),
|
||||||
new("Peasant"),
|
new(1078479, "the peasant"),
|
||||||
new("Pirate"),
|
new(1078480, "the pirate"),
|
||||||
new("Prisoner"),
|
new(1078481, "the prisoner"),
|
||||||
new("Prophet"),
|
new(1078482, "the prophet"),
|
||||||
new("Ranger"),
|
new(1078484, "the ranger"),
|
||||||
new("Sage"),
|
new(1078487, "the sage"),
|
||||||
new("Sailor"),
|
new(1078488, "the sailor"),
|
||||||
new("Scholar"),
|
new(1078489, "the scholar"),
|
||||||
new("Scribe"),
|
new(1060773, "the scribe"),
|
||||||
new("Sentry"),
|
new(1078490, "the sentry"),
|
||||||
new("Servant"),
|
new(1060795, "the servant"),
|
||||||
new("Shepherd"),
|
new(1078491, "the shepherd"),
|
||||||
new("Soothsayer"),
|
new(1078492, "the soothsayer"),
|
||||||
new("Stoic"),
|
new(1078493, "the stoic"),
|
||||||
new("Storyteller"),
|
new(1078494, "the storyteller"),
|
||||||
new("Tailor"),
|
new(1076134, "the tailor"),
|
||||||
new("Thief"),
|
new(1076096, "the thief"),
|
||||||
new("Tinker"),
|
new(1076137, "the tinker"),
|
||||||
new("Town Crier"),
|
new(1076097, "the town crier"),
|
||||||
new("Treasure Hunter"),
|
new(1073291, "the treasure hunter"),
|
||||||
new("Waiter", true),
|
new(1076112, "the waiter", true),
|
||||||
new("Warrior"),
|
new(1077242, "the warrior"),
|
||||||
new("Watchman"),
|
new(1078496, "the watchman"),
|
||||||
new("No Title", null)
|
new(1078495, null) // No Title
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly PlayerBarkeeper m_Barkeeper;
|
private static int _pageCount = (_entries.Length + 19) / 20;
|
||||||
private readonly Mobile m_From;
|
|
||||||
|
private readonly PlayerBarkeeper _barkeeper;
|
||||||
|
private readonly Mobile _from;
|
||||||
|
|
||||||
public override bool Singleton => true;
|
public override bool Singleton => true;
|
||||||
|
|
||||||
public BarkeeperTitleGump(Mobile from, PlayerBarkeeper barkeeper) : base(0, 0)
|
protected override void BuildLayout(ref StaticGumpBuilder builder)
|
||||||
{
|
{
|
||||||
m_From = from;
|
RenderBackground(ref builder);
|
||||||
m_Barkeeper = barkeeper;
|
|
||||||
|
|
||||||
from.CloseGump<BarkeeperGump>();
|
for (var i = 0; i < _pageCount; ++i)
|
||||||
|
|
||||||
var entries = m_Entries;
|
|
||||||
|
|
||||||
RenderBackground();
|
|
||||||
|
|
||||||
var pageCount = (entries.Length + 19) / 20;
|
|
||||||
|
|
||||||
for (var i = 0; i < pageCount; ++i)
|
|
||||||
{
|
{
|
||||||
RenderPage(entries, i);
|
RenderPage(ref builder, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderBackground()
|
public BarkeeperTitleGump(Mobile from, PlayerBarkeeper barkeeper) : base(0, 0)
|
||||||
{
|
{
|
||||||
AddPage(0);
|
_from = from;
|
||||||
|
_barkeeper = barkeeper;
|
||||||
AddBackground(30, 40, 585, 410, 5054);
|
|
||||||
|
|
||||||
AddImage(30, 40, 9251);
|
|
||||||
AddImage(180, 40, 9251);
|
|
||||||
AddImage(30, 40, 9253);
|
|
||||||
AddImage(30, 130, 9253);
|
|
||||||
AddImage(598, 40, 9255);
|
|
||||||
AddImage(598, 130, 9255);
|
|
||||||
AddImage(30, 433, 9257);
|
|
||||||
AddImage(180, 433, 9257);
|
|
||||||
AddImage(30, 40, 9250);
|
|
||||||
AddImage(598, 40, 9252);
|
|
||||||
AddImage(598, 433, 9258);
|
|
||||||
AddImage(30, 433, 9256);
|
|
||||||
|
|
||||||
AddItem(30, 40, 6816);
|
|
||||||
AddItem(30, 125, 6817);
|
|
||||||
AddItem(30, 233, 6817);
|
|
||||||
AddItem(30, 341, 6817);
|
|
||||||
AddItem(580, 40, 6814);
|
|
||||||
AddItem(588, 125, 6815);
|
|
||||||
AddItem(588, 233, 6815);
|
|
||||||
AddItem(588, 341, 6815);
|
|
||||||
|
|
||||||
AddImage(560, 20, 1417);
|
|
||||||
AddItem(580, 44, 4033);
|
|
||||||
|
|
||||||
AddBackground(183, 25, 280, 30, 5054);
|
|
||||||
|
|
||||||
AddImage(180, 25, 10460);
|
|
||||||
AddImage(434, 25, 10460);
|
|
||||||
|
|
||||||
AddHtml(223, 32, 200, 40, "BARKEEP CUSTOMIZATION MENU");
|
|
||||||
AddBackground(243, 433, 150, 30, 5054);
|
|
||||||
|
|
||||||
AddImage(240, 433, 10460);
|
|
||||||
AddImage(375, 433, 10460);
|
|
||||||
|
|
||||||
AddImage(80, 398, 2151);
|
|
||||||
AddItem(72, 406, 2543);
|
|
||||||
|
|
||||||
AddHtml(110, 412, 180, 25, "sells food and drink");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderPage(Entry[] entries, int page)
|
private static void RenderBackground(ref StaticGumpBuilder builder)
|
||||||
{
|
{
|
||||||
AddPage(1 + page);
|
builder.AddPage();
|
||||||
|
|
||||||
AddHtml(430, 70, 180, 25, $"Page {page + 1} of {(entries.Length + 19) / 20}");
|
builder.AddBackground(30, 40, 585, 410, 5054);
|
||||||
|
|
||||||
for (int count = 0, i = page * 20; count < 20 && i < entries.Length; ++count, ++i)
|
builder.AddImage(30, 40, 9251);
|
||||||
|
builder.AddImage(180, 40, 9251);
|
||||||
|
builder.AddImage(30, 40, 9253);
|
||||||
|
builder.AddImage(30, 130, 9253);
|
||||||
|
builder.AddImage(598, 40, 9255);
|
||||||
|
builder.AddImage(598, 130, 9255);
|
||||||
|
builder.AddImage(30, 433, 9257);
|
||||||
|
builder.AddImage(180, 433, 9257);
|
||||||
|
builder.AddImage(30, 40, 9250);
|
||||||
|
builder.AddImage(598, 40, 9252);
|
||||||
|
builder.AddImage(598, 433, 9258);
|
||||||
|
builder.AddImage(30, 433, 9256);
|
||||||
|
|
||||||
|
builder.AddItem(30, 40, 6816);
|
||||||
|
builder.AddItem(30, 125, 6817);
|
||||||
|
builder.AddItem(30, 233, 6817);
|
||||||
|
builder.AddItem(30, 341, 6817);
|
||||||
|
builder.AddItem(580, 40, 6814);
|
||||||
|
builder.AddItem(588, 125, 6815);
|
||||||
|
builder.AddItem(588, 233, 6815);
|
||||||
|
builder.AddItem(588, 341, 6815);
|
||||||
|
|
||||||
|
builder.AddImage(560, 20, 1417);
|
||||||
|
builder.AddItem(580, 44, 4033);
|
||||||
|
|
||||||
|
builder.AddBackground(183, 25, 280, 30, 5054);
|
||||||
|
|
||||||
|
builder.AddImage(180, 25, 10460);
|
||||||
|
builder.AddImage(434, 25, 10460);
|
||||||
|
|
||||||
|
builder.AddHtmlLocalized(223, 32, 200, 40, 1078366); // BARKEEP CUSTOMIZATION MENU
|
||||||
|
builder.AddBackground(243, 433, 150, 30, 5054);
|
||||||
|
|
||||||
|
builder.AddImage(240, 433, 10460);
|
||||||
|
builder.AddImage(375, 433, 10460);
|
||||||
|
|
||||||
|
builder.AddImage(80, 398, 2151);
|
||||||
|
builder.AddItem(72, 406, 2543);
|
||||||
|
|
||||||
|
builder.AddHtmlLocalized(110, 412, 180, 25, 1078445); // sells food and drink
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderPage(ref StaticGumpBuilder builder, int page)
|
||||||
|
{
|
||||||
|
var currentPage = page + 1;
|
||||||
|
builder.AddPage(currentPage);
|
||||||
|
|
||||||
|
if (_pageCount == 3 && currentPage is >= 1 and <= 3)
|
||||||
{
|
{
|
||||||
var entry = entries[i];
|
var pageCliloc = currentPage switch
|
||||||
|
{
|
||||||
|
1 => 1078439, // Page 1 of 3
|
||||||
|
2 => 1078464, // Page 2 of 3
|
||||||
|
3 => 1078483, // Page 3 of 3
|
||||||
|
};
|
||||||
|
|
||||||
AddButton(80 + count / 10 * 260, 100 + count % 10 * 30, 4005, 4007, 2 + i);
|
builder.AddHtmlLocalized(430, 70, 180, 25, pageCliloc);
|
||||||
AddHtml(
|
}
|
||||||
120 + count / 10 * 260,
|
else
|
||||||
100 + count % 10 * 30,
|
{
|
||||||
entry.m_Vendor ? 148 : 180,
|
// Page ~1_CUR~ of ~2_MAX~
|
||||||
|
builder.AddHtmlLocalized(430, 70, 180, 25, 1153561, $"{currentPage}\t{_pageCount}", 0x7FFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int count = 0, i = page * 20; count < 20 && i < _entries.Length; ++count, ++i)
|
||||||
|
{
|
||||||
|
var entry = _entries[i];
|
||||||
|
|
||||||
|
var xOffset = Math.DivRem(count, 10, out var yOffset);
|
||||||
|
|
||||||
|
builder.AddButton(80 + xOffset * 260, 100 + yOffset * 30, 4005, 4007, 2 + i);
|
||||||
|
builder.AddHtmlLocalized(
|
||||||
|
120 + xOffset * 260,
|
||||||
|
100 + yOffset * 30,
|
||||||
|
entry.Vendor ? 148 : 180,
|
||||||
25,
|
25,
|
||||||
entry.m_Description,
|
entry.Description,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
if (entry.m_Vendor)
|
if (entry.Vendor)
|
||||||
{
|
{
|
||||||
AddImage(270 + count / 10 * 260, 98 + count % 10 * 30, 2151);
|
builder.AddImage(270 + xOffset * 260, 98 + yOffset * 30, 2151);
|
||||||
AddItem(262 + count / 10 * 260, 106 + count % 10 * 30, 2543);
|
builder.AddItem(262 + xOffset * 260, 106 + yOffset * 30, 2543);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddButton(340, 400, 4005, 4007, 0, GumpButtonType.Page, 1 + (page + 1) % ((entries.Length + 19) / 20));
|
builder.AddButton(340, 400, 4005, 4007, 0, GumpButtonType.Page, currentPage + 1 % _pageCount);
|
||||||
AddHtml(380, 400, 180, 25, "More Job Titles");
|
builder.AddHtmlLocalized(380, 400, 180, 25, 1078456); // More Job Titles
|
||||||
|
|
||||||
AddButton(338, 437, 4014, 4016, 1);
|
builder.AddButton(338, 437, 4014, 4016, 1);
|
||||||
AddHtml(290, 440, 35, 40, "Back");
|
builder.AddHtmlLocalized(290, 440, 35, 40, 1005007); // Back
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnResponse(NetState sender, in RelayInfo info)
|
public override void OnResponse(NetState sender, in RelayInfo info)
|
||||||
{
|
{
|
||||||
|
if (_barkeeper.Deleted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var buttonID = info.ButtonID;
|
var buttonID = info.ButtonID;
|
||||||
|
|
||||||
if (buttonID > 0)
|
if (buttonID-- <= 0)
|
||||||
{
|
{
|
||||||
--buttonID;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (buttonID > 0)
|
if (buttonID-- <= 0)
|
||||||
{
|
{
|
||||||
--buttonID;
|
_barkeeper.CancelChangeTitle(_from);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (buttonID < m_Entries.Length)
|
if (buttonID < _entries.Length)
|
||||||
{
|
{
|
||||||
m_Barkeeper.EndChangeTitle(m_From, m_Entries[buttonID].m_Title, m_Entries[buttonID].m_Vendor);
|
var entry = _entries[buttonID];
|
||||||
}
|
_barkeeper.EndChangeTitle(_from, entry.Title, entry.Vendor);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_Barkeeper.CancelChangeTitle(m_From);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Entry
|
private class Entry
|
||||||
{
|
{
|
||||||
public readonly string m_Description;
|
public readonly int Description;
|
||||||
public readonly string m_Title;
|
public readonly string Title;
|
||||||
public readonly bool m_Vendor;
|
public readonly bool Vendor;
|
||||||
|
|
||||||
public Entry(string desc, bool vendor = false) : this(desc, $"the {desc.ToLower()}", vendor)
|
public Entry(int desc, string title, bool vendor = false)
|
||||||
{
|
{
|
||||||
}
|
Description = desc;
|
||||||
|
Title = title;
|
||||||
public Entry(string desc, string title, bool vendor = false)
|
Vendor = vendor;
|
||||||
{
|
|
||||||
m_Description = desc;
|
|
||||||
m_Title = title;
|
|
||||||
m_Vendor = vendor;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1140,6 +1140,11 @@ namespace Server.Spells
|
||||||
|
|
||||||
protected override void OnTick()
|
protected override void OnTick()
|
||||||
{
|
{
|
||||||
|
if (m_Target.Deleted || !m_Target.Alive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Damage(
|
Damage(
|
||||||
m_Spell,
|
m_Spell,
|
||||||
TimeSpan.Zero,
|
TimeSpan.Zero,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue