mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-29 15:49:00 +01:00
Add per player chunk loading limits
Configurable under "settings.chunk-loading.player-max-chunk-load-rate", defaults to -1. This commit also changes the chunk loading to be distributed equally for all players, rather than distance based. This is to ensure players flying around do not take priority over everyone else. The exception to this new rule is the min-load-radius, which still has priority over everything else.
This commit is contained in:
parent
5c7b445c76
commit
a096540d94
6 changed files with 124 additions and 19 deletions
|
@ -15,7 +15,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
Entity entity = this.player.getRootVehicle();
|
Entity entity = this.player.getRootVehicle();
|
||||||
|
|
||||||
+ // Paper start
|
+ // Paper start
|
||||||
+ if (this.awaitingPositionFromClient != null) {
|
+ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) {
|
||||||
+ return;
|
+ return;
|
||||||
+ }
|
+ }
|
||||||
+ // Paper end
|
+ // Paper end
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||||
|
Date: Thu, 31 Mar 2022 05:11:37 -0700
|
||||||
|
Subject: [PATCH] Ensure entity passenger world matches ridden entity
|
||||||
|
|
||||||
|
Bad plugins doing this would cause some obvious problems...
|
||||||
|
|
||||||
|
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
|
||||||
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||||
|
--- a/src/main/java/net/minecraft/world/entity/Entity.java
|
||||||
|
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
|
||||||
|
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean addPassenger(Entity entity) { // CraftBukkit
|
||||||
|
+ // Paper start
|
||||||
|
+ if (entity.level != this.level) {
|
||||||
|
+ throw new IllegalArgumentException("Entity passenger world must match");
|
||||||
|
+ }
|
||||||
|
+ // Paper end
|
||||||
|
if (entity == this) throw new IllegalArgumentException("Entities cannot become a passenger of themselves"); // Paper - issue 572
|
||||||
|
if (entity.getVehicle() != this) {
|
||||||
|
throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)");
|
45
patches/server/Guard-against-invalid-entity-positions.patch
Normal file
45
patches/server/Guard-against-invalid-entity-positions.patch
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||||
|
Date: Thu, 31 Mar 2022 05:18:28 -0700
|
||||||
|
Subject: [PATCH] Guard against invalid entity positions
|
||||||
|
|
||||||
|
Anything not finite should be blocked and logged
|
||||||
|
|
||||||
|
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
|
||||||
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||||
|
--- a/src/main/java/net/minecraft/world/entity/Entity.java
|
||||||
|
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
|
||||||
|
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
|
||||||
|
return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // Paper start - block invalid positions
|
||||||
|
+ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) {
|
||||||
|
+ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) {
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ String entityInfo = null;
|
||||||
|
+ try {
|
||||||
|
+ entityInfo = entity.toString();
|
||||||
|
+ } catch (Exception ex) {
|
||||||
|
+ entityInfo = "[Entity info unavailable] ";
|
||||||
|
+ }
|
||||||
|
+ LOGGER.error("New entity position is invalid! Tried to set invalid position (" + newX + "," + newY + "," + newZ + ") for entity " + entity.getClass().getName() + " located at " + entity.position + ", entity info: " + entityInfo, new Throwable());
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+ // Paper end - block invalid positions
|
||||||
|
+
|
||||||
|
public final void setPosRaw(double x, double y, double z) {
|
||||||
|
// Paper start
|
||||||
|
this.setPosRaw(x, y, z, false);
|
||||||
|
}
|
||||||
|
public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
|
||||||
|
+ // Paper start - block invalid positions
|
||||||
|
+ if (!checkPosition(this, x, y, z)) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ // Paper end - block invalid positions
|
||||||
|
// Paper end
|
||||||
|
// Paper start - fix MC-4
|
||||||
|
if (this instanceof ItemEntity) {
|
|
@ -2899,14 +2899,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+public final class IntervalledCounter {
|
+public final class IntervalledCounter {
|
||||||
+
|
+
|
||||||
+ protected long[] times;
|
+ protected long[] times;
|
||||||
|
+ protected long[] counts;
|
||||||
+ protected final long interval;
|
+ protected final long interval;
|
||||||
+ protected long minTime;
|
+ protected long minTime;
|
||||||
+ protected int sum;
|
+ protected long sum;
|
||||||
+ protected int head; // inclusive
|
+ protected int head; // inclusive
|
||||||
+ protected int tail; // exclusive
|
+ protected int tail; // exclusive
|
||||||
+
|
+
|
||||||
+ public IntervalledCounter(final long interval) {
|
+ public IntervalledCounter(final long interval) {
|
||||||
+ this.times = new long[8];
|
+ this.times = new long[8];
|
||||||
|
+ this.counts = new long[8];
|
||||||
+ this.interval = interval;
|
+ this.interval = interval;
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
|
@ -2915,7 +2917,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ public void updateCurrentTime(final long currentTime) {
|
+ public void updateCurrentTime(final long currentTime) {
|
||||||
+ int sum = this.sum;
|
+ long sum = this.sum;
|
||||||
+ int head = this.head;
|
+ int head = this.head;
|
||||||
+ final int tail = this.tail;
|
+ final int tail = this.tail;
|
||||||
+ final long minTime = currentTime - this.interval;
|
+ final long minTime = currentTime - this.interval;
|
||||||
|
@ -2924,8 +2926,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+
|
+
|
||||||
+ // guard against overflow by using subtraction
|
+ // guard against overflow by using subtraction
|
||||||
+ while (head != tail && this.times[head] - minTime < 0) {
|
+ while (head != tail && this.times[head] - minTime < 0) {
|
||||||
+ head = (head + 1) % arrayLen;
|
+ sum -= this.counts[head];
|
||||||
+ --sum;
|
+ // there are two ways we can do this:
|
||||||
|
+ // 1. free the count when adding
|
||||||
|
+ // 2. free it now
|
||||||
|
+ // option #2
|
||||||
|
+ this.counts[head] = 0;
|
||||||
|
+ if (++head >= arrayLen) {
|
||||||
|
+ head = 0;
|
||||||
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ this.sum = sum;
|
+ this.sum = sum;
|
||||||
|
@ -2934,6 +2943,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ public void addTime(final long currTime) {
|
+ public void addTime(final long currTime) {
|
||||||
|
+ this.addTime(currTime, 1L);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public void addTime(final long currTime, final long count) {
|
||||||
+ // guard against overflow by using subtraction
|
+ // guard against overflow by using subtraction
|
||||||
+ if (currTime - this.minTime < 0) {
|
+ if (currTime - this.minTime < 0) {
|
||||||
+ return;
|
+ return;
|
||||||
|
@ -2945,28 +2958,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ this.times[this.tail] = currTime;
|
+ this.times[this.tail] = currTime;
|
||||||
|
+ this.counts[this.tail] += count;
|
||||||
|
+ this.sum += count;
|
||||||
+ this.tail = nextTail;
|
+ this.tail = nextTail;
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ public void updateAndAdd(final int count) {
|
+ public void updateAndAdd(final int count) {
|
||||||
+ final long currTime = System.nanoTime();
|
+ final long currTime = System.nanoTime();
|
||||||
+ this.updateCurrentTime(currTime);
|
+ this.updateCurrentTime(currTime);
|
||||||
+ for (int i = 0; i < count; ++i) {
|
+ this.addTime(currTime, count);
|
||||||
+ this.addTime(currTime);
|
|
||||||
+ }
|
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ public void updateAndAdd(final int count, final long currTime) {
|
+ public void updateAndAdd(final int count, final long currTime) {
|
||||||
+ this.updateCurrentTime(currTime);
|
+ this.updateCurrentTime(currTime);
|
||||||
+ for (int i = 0; i < count; ++i) {
|
+ this.addTime(currTime, count);
|
||||||
+ this.addTime(currTime);
|
|
||||||
+ }
|
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ private void resize() {
|
+ private void resize() {
|
||||||
+ final long[] oldElements = this.times;
|
+ final long[] oldElements = this.times;
|
||||||
|
+ final long[] oldCounts = this.counts;
|
||||||
+ final long[] newElements = new long[this.times.length * 2];
|
+ final long[] newElements = new long[this.times.length * 2];
|
||||||
|
+ final long[] newCounts = new long[this.times.length * 2];
|
||||||
+ this.times = newElements;
|
+ this.times = newElements;
|
||||||
|
+ this.counts = newCounts;
|
||||||
+
|
+
|
||||||
+ final int head = this.head;
|
+ final int head = this.head;
|
||||||
+ final int tail = this.tail;
|
+ final int tail = this.tail;
|
||||||
|
@ -2976,9 +2990,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+
|
+
|
||||||
+ if (tail >= head) {
|
+ if (tail >= head) {
|
||||||
+ System.arraycopy(oldElements, head, newElements, 0, size);
|
+ System.arraycopy(oldElements, head, newElements, 0, size);
|
||||||
|
+ System.arraycopy(oldCounts, head, newCounts, 0, size);
|
||||||
+ } else {
|
+ } else {
|
||||||
+ System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head);
|
+ System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head);
|
||||||
+ System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail);
|
+ System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail);
|
||||||
|
+
|
||||||
|
+ System.arraycopy(oldCounts, head, newCounts, 0, oldCounts.length - head);
|
||||||
|
+ System.arraycopy(oldCounts, 0, newCounts, oldCounts.length - head, tail);
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
|
@ -2987,11 +3005,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+ return this.size() / (this.interval * 1.0e-9);
|
+ return this.size() / (this.interval * 1.0e-9);
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ public int size() {
|
+ public long size() {
|
||||||
+ final int head = this.head;
|
+ return this.sum;
|
||||||
+ final int tail = this.tail;
|
|
||||||
+
|
|
||||||
+ return tail >= head ? (tail - head) : (tail + (this.times.length - head));
|
|
||||||
+ }
|
+ }
|
||||||
+}
|
+}
|
||||||
diff --git a/src/main/java/io/papermc/paper/util/WorldUtil.java b/src/main/java/io/papermc/paper/util/WorldUtil.java
|
diff --git a/src/main/java/io/papermc/paper/util/WorldUtil.java b/src/main/java/io/papermc/paper/util/WorldUtil.java
|
||||||
|
|
|
@ -14,8 +14,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
||||||
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
||||||
@@ -0,0 +0,0 @@ public class PaperConfig {
|
@@ -0,0 +0,0 @@ public class PaperConfig {
|
||||||
}
|
|
||||||
globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0);
|
globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0);
|
||||||
|
playerMaxChunkLoadRate = getDouble("settings.chunk-loading.player-max-chunk-load-rate", -1.0);
|
||||||
}
|
}
|
||||||
+
|
+
|
||||||
+ public static boolean useDimensionTypeForCustomSpawners;
|
+ public static boolean useDimensionTypeForCustomSpawners;
|
||||||
|
|
|
@ -102,6 +102,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+ public static double globalMaxChunkLoadRate;
|
+ public static double globalMaxChunkLoadRate;
|
||||||
+ public static double playerMaxConcurrentChunkLoads;
|
+ public static double playerMaxConcurrentChunkLoads;
|
||||||
+ public static double globalMaxConcurrentChunkLoads;
|
+ public static double globalMaxConcurrentChunkLoads;
|
||||||
|
+ public static double playerMaxChunkLoadRate;
|
||||||
+
|
+
|
||||||
+ private static void newPlayerChunkManagement() {
|
+ private static void newPlayerChunkManagement() {
|
||||||
+ playerMinChunkLoadRadius = getInt("settings.chunk-loading.min-load-radius", 2);
|
+ playerMinChunkLoadRadius = getInt("settings.chunk-loading.min-load-radius", 2);
|
||||||
|
@ -119,6 +120,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+ set("settings.chunk-loading.player-max-concurrent-loads", playerMaxConcurrentChunkLoads = 20.0);
|
+ set("settings.chunk-loading.player-max-concurrent-loads", playerMaxConcurrentChunkLoads = 20.0);
|
||||||
+ }
|
+ }
|
||||||
+ globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0);
|
+ globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0);
|
||||||
|
+ playerMaxChunkLoadRate = getDouble("settings.chunk-loading.player-max-chunk-load-rate", -1.0);
|
||||||
+ }
|
+ }
|
||||||
}
|
}
|
||||||
diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
|
diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
|
||||||
|
@ -218,10 +220,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+
|
+
|
||||||
+ final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority);
|
+ final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority);
|
||||||
+
|
+
|
||||||
+ if (priorityCompare != 0) {
|
+ final int lastLoadTimeCompare = Long.compare(p1.lastChunkLoad, p2.lastChunkLoad);
|
||||||
|
+
|
||||||
|
+ if ((holder1 == null || holder2 == null || lastLoadTimeCompare == 0 || holder1.priority < 0.0 || holder2.priority < 0.0) && priorityCompare != 0) {
|
||||||
+ return priorityCompare;
|
+ return priorityCompare;
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
|
+ if (lastLoadTimeCompare != 0) {
|
||||||
|
+ return lastLoadTimeCompare;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId());
|
+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId());
|
||||||
+
|
+
|
||||||
+ if (idCompare != 0) {
|
+ if (idCompare != 0) {
|
||||||
|
@ -744,6 +752,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+ for (;;) {
|
+ for (;;) {
|
||||||
+ final PlayerLoaderData data = this.chunkLoadQueue.pollFirst();
|
+ final PlayerLoaderData data = this.chunkLoadQueue.pollFirst();
|
||||||
+
|
+
|
||||||
|
+ data.lastChunkLoad = time;
|
||||||
|
+
|
||||||
+ final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst();
|
+ final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst();
|
||||||
+ if (queuedLoad == null) {
|
+ if (queuedLoad == null) {
|
||||||
+ if (this.chunkLoadQueue.isEmpty()) {
|
+ if (this.chunkLoadQueue.isEmpty()) {
|
||||||
|
@ -756,6 +766,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+ updatedCounters = true;
|
+ updatedCounters = true;
|
||||||
+ TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time);
|
+ TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time);
|
||||||
+ TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time);
|
+ TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time);
|
||||||
|
+ data.ticketAdditionCounterShort.updateCurrentTime(time);
|
||||||
|
+ data.ticketAdditionCounterLong.updateCurrentTime(time);
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
|
+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
|
||||||
|
@ -791,7 +803,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+ // priority >= 0.0 implies rate limited chunks
|
+ // priority >= 0.0 implies rate limited chunks
|
||||||
+
|
+
|
||||||
+ final int currentChunkLoads = this.concurrentChunkLoads;
|
+ final int currentChunkLoads = this.concurrentChunkLoads;
|
||||||
+ if (currentChunkLoads >= maxLoads || (PaperConfig.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= PaperConfig.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= PaperConfig.globalMaxChunkLoadRate))) {
|
+ if (currentChunkLoads >= maxLoads || (PaperConfig.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= PaperConfig.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= PaperConfig.globalMaxChunkLoadRate))
|
||||||
|
+ || (PaperConfig.playerMaxChunkLoadRate > 0.0 && (data.ticketAdditionCounterShort.getRate() >= PaperConfig.playerMaxChunkLoadRate || data.ticketAdditionCounterLong.getRate() >= PaperConfig.playerMaxChunkLoadRate))) {
|
||||||
+ // don't poll, we didn't load it
|
+ // don't poll, we didn't load it
|
||||||
+ this.chunkLoadQueue.add(data);
|
+ this.chunkLoadQueue.add(data);
|
||||||
+ break;
|
+ break;
|
||||||
|
@ -821,6 +834,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+ ++this.concurrentChunkLoads;
|
+ ++this.concurrentChunkLoads;
|
||||||
+ TICKET_ADDITION_COUNTER_SHORT.addTime(time);
|
+ TICKET_ADDITION_COUNTER_SHORT.addTime(time);
|
||||||
+ TICKET_ADDITION_COUNTER_LONG.addTime(time);
|
+ TICKET_ADDITION_COUNTER_LONG.addTime(time);
|
||||||
|
+ data.ticketAdditionCounterShort.addTime(time);
|
||||||
|
+ data.ticketAdditionCounterLong.addTime(time);
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
|
@ -926,6 +941,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||||
+
|
+
|
||||||
+ protected long nextChunkSendTarget;
|
+ protected long nextChunkSendTarget;
|
||||||
+
|
+
|
||||||
|
+ // this interval prevents bursting a lot of chunk loads
|
||||||
|
+ protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms
|
||||||
|
+ // this ensures the rate is kept between ticks correctly
|
||||||
|
+ protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms
|
||||||
|
+
|
||||||
|
+ public long lastChunkLoad;
|
||||||
|
+
|
||||||
+ public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) {
|
+ public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) {
|
||||||
+ this.player = player;
|
+ this.player = player;
|
||||||
+ this.loader = loader;
|
+ this.loader = loader;
|
||||||
|
|
Loading…
Reference in a new issue