Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/main/java/com/reider745/event/EventListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,11 @@ public void onEntityTeleport(EntityTeleportEvent event) {
NativeCallback.onCustomDimensionTransfer(EntityMethod.getIdForEntity(event.getEntity()), from, to);
}

@EventHandler(priority = EventPriority.HIGHEST)
public void onChat(PlayerChatEvent event) {
consumeEvent(event, () -> NativeCallback.onServerChat(event.getPlayer().getNameTag(), event.getMessage(), EntityMethod.getIdForEntity(event.getPlayer())));
}

@EventHandler(priority = EventPriority.HIGHEST)
public void onServerCommand(ServerCommandEvent event) {
final CommandSender sender = event.getSender();
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/zhekasmirnov/horizon/util/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -155,4 +156,12 @@ public static void copyFileTree(File srcDir, File dstDir, Object dialog, String
}
}
}

public static String convertStreamToString(InputStream inputStream) {
try {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
return "";
}
}
}
23 changes: 21 additions & 2 deletions src/main/java/com/zhekasmirnov/innercore/api/NativeCallback.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import org.innercore.icstd.region.BlockSource;
import org.innercore.icstd.region.BlockState;
import org.innercore.icstd.scene.GameObjectManager;
import org.innercore.icstd.scene.GameObjectScope;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;

Expand Down Expand Up @@ -326,7 +327,8 @@ public static void onGameStopped(boolean isServer) {
// on server side save all data
WorldDataSaverHandler.getInstance().onLevelLeft();

GameObjectManager.clearAllDefaultServerScenes(false);
GameObjectManager.clearAllDefaultServerScenes(true, false);
GameObjectScope.SERVER_SCOPE.expireAllHandles();

// legacy callback for server left
if (callLegacyCallback) {
Expand Down Expand Up @@ -541,7 +543,24 @@ public static void onExplode(float x, float y, float z, float power, long entity
);
}

@ZoteOnly
@ClientOnly
public static void onLocalChatSent() {
String message = getStringParam("chat_message");
Callback.invokeAPICallback("LocalChatSent", message);
}

@ClientOnly
public static void onLocalChatReceived(boolean debounced) {
String author = getStringParam("local_chat_author");
String message = getStringParam("local_chat_message");
Callback.invokeAPICallback("LocalChatReceived", author, message, debounced);
}

public static void onServerChat(String author, String message, long player) {
Callback.invokeAPICallback("ServerChat", author, message, player);
}


public static void onServerCommand(String command, float x, float y, float z, long entity, boolean fromPacket) {
Callback.invokeAPICallback("ServerCommand", command, new GenericScriptPos3(x, y, z), entity, fromPacket);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ public void read(IScopedReader io, SerializationContext ctx) {
return;
}
for (String mobKey : allScales.iterKeys()) {
final long mob = Long.parseLong(mobKey);
final long mob;
try {
mob = Long.parseLong(mobKey);
} catch (NumberFormatException e) {
ctx.addError("invalid mob uid key in scales scope: " + mobKey);
continue;
}

IObjectReader mobScales = allScales.beginScope(mobKey).asObject();
if (mobScales != null) {
for (String scaleId : mobScales.iterKeys()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.innercore.icstd.entity.PathNavigation;
import org.innercore.icstd.inventory.ItemInstance;
import org.innercore.icstd.inventory.ItemInstanceExtra;
import org.innercore.icstd.serialization.ObjectSerializer;
import org.innercore.icstd.serialization.SerializationContext;
import org.json.JSONException;
import org.mozilla.javascript.*;
Expand Down Expand Up @@ -2194,7 +2195,13 @@ public static String registerObjectSaver(final String name, Object scopeSaver) {
ObjectSaverRegistry.registerSaver(name, new ObjectSaver() {
@Override
public Object read(ScriptableObject input) {
return saver.read(input);
Object result = saver.read(input);
// rhino coerces js undefined into the string "undefined" for object-typed
// interface methods, read() returning undefined means null
if ("undefined".equals(result)) {
return null;
}
return result;
}

@Override
Expand All @@ -2218,10 +2225,25 @@ public static Object deserializeFromString(String param) {
}

@JSStaticFunction
public static void registerObject(Object obj, String saverId) {
ObjectSaverRegistry.registerObject(obj, saverId);
public static void registerObject(Object obj, Object saverId) {
String typeId;
if (saverId instanceof Number) {
// legacy numeric saver id, e.g. stored by an old mod
typeId = ObjectSerializer.getTypeIdFromLegacyHashId(((Number) saverId).intValue(), null);
} else {
typeId = saverId != null ? saverId.toString() : null;
}
ObjectSaverRegistry.registerObject(obj, typeId);
}

@Deprecated
@JSStaticFunction
public static Object getObjectSaverId(Object obj) {
String typeId = ObjectSerializer.getSerializerTypeIdFromObject(obj);
return typeId != null ? typeId : -1;
}


@JSStaticFunction
public static void setObjectIgnored(ScriptableObject obj, boolean ignore) {
// NOTE: This was not working for a long time already
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ public void write(IScopedWriter io, SerializationContext ctx) {
@Override
public void read(IScopedReader io, SerializationContext ctx) {
IArrayReader array = io.asArray();
if (array == null) {
io.endScope();
return;
}
int successfullyLoaded = 0;
for (int i = 0; i < array.size(); i++) {
Object possibleUpdatable = ObjectSerializer.deserializeObject(array.beginScope(i), Object.class, ctx, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,21 @@ public synchronized File readBackupAfterMainFailed() {
return null;
if (state != State.READING)
throw new IllegalStateException("readBackupAfterMainFailed(): not in correct state " + state);
mainFile.delete();
// quarantine the corrupt main file for manual recovery - it must not stay in place, or the
// next successful write would rotate it into the backup
File corruptFile = new File(mainFile.getAbsolutePath() + ".corrupt");
corruptFile.delete();
if (!mainFile.renameTo(corruptFile)) {
mainFile.delete();
}

state = State.READING_BACKUP;
return backupFile;
}

public synchronized void endRead() {
if (state != State.READING && state != State.READING_BACKUP)
throw new IllegalStateException("endRead(): not in correct state " + state);
return;
state = State.IDLE;
}

Expand All @@ -51,10 +58,10 @@ public synchronized File beginWrite() {
return tmpFile;
}

// NOTE: on failure the state remains WRITING and the caller must call abortWrite()
public synchronized boolean endSuccessfulWrite(boolean writeToLog) {
if (state != State.WRITING)
throw new IllegalStateException("endSuccessfulWrite(): not in correct state " + state);
state = State.IDLE;
if (mainFile.isFile()) { // main file is valid
backupFile.delete();
if (!mainFile.renameTo(backupFile)) {
Expand All @@ -66,17 +73,18 @@ public synchronized boolean endSuccessfulWrite(boolean writeToLog) {
return false;
}
if (tmpFile.renameTo(mainFile)) {
state = State.IDLE;
if (writeToLog) ICLog.d("SAVER", "renamed saves file " + tmpFile.getAbsolutePath() + " to " + mainFile.getName());
return true;
} else {
if (writeToLog) ICLog.d("SAVER", "failed to rename " + mainFile.getAbsolutePath() + " to " + backupFile.getName());
if (writeToLog) ICLog.d("SAVER", "failed to rename " + tmpFile.getAbsolutePath() + " to " + mainFile.getName());
return false;
}
}

public synchronized void abortWrite() {
if (state != State.WRITING)
throw new IllegalStateException("abortWrite(): not in correct state " + state);
return;
state = State.IDLE;
tmpFile.delete();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public enum SaverState {
private final File worldDirectory;
private final CriticalDataFile savesFile;
private final Map<String, Object> missingScopeData = new HashMap<>();
private final WorldDataScopeRegistry.RetainedScopeData retainedScopeData = new WorldDataScopeRegistry.RetainedScopeData();


private SaverState state = SaverState.IDLE;
private boolean savingErrorsReportedAsDialog = false;
Expand Down Expand Up @@ -79,12 +81,19 @@ public synchronized void readAllData(boolean showLogOnError) {
return;
}
savingErrorsReportedAsDialog = false;
missingScopeData.clear();
retainedScopeData.clear();

JSONObject json = readJsonImpl(savesFile.beginRead());
if (json == null) {
json = readJsonImpl(savesFile.readBackupAfterMainFailed());
JSONObject json;
try {
json = readJsonImpl(savesFile.beginRead());
if (json == null) {
json = readJsonImpl(savesFile.readBackupAfterMainFailed());
}
} finally {
savesFile.endRead();
}
savesFile.endRead();

if (json == null) {
json = readJsonWithLockCheck(worldDirectory, LEGACY_MAIN_SAVES_BACKUP, false);
}
Expand All @@ -93,9 +102,8 @@ public synchronized void readAllData(boolean showLogOnError) {
}

state = SaverState.READ;
missingScopeData.clear();
SerializationContext ctx = new SerializationContext();
WorldDataScopeRegistry.getInstance().readAllScopes(json, ctx, missingScopeData);
WorldDataScopeRegistry.getInstance().readAllScopes(json, ctx, retainedScopeData, missingScopeData);
state = SaverState.IDLE;

if (showLogOnError) {
Expand All @@ -118,11 +126,11 @@ public synchronized void saveAllData(boolean showLogOnError) {
state = SaverState.SAVE;
JSONObject json = new JSONObject(missingScopeData);
SerializationContext ctx = new SerializationContext();
WorldDataScopeRegistry.getInstance().saveAllScopes(json, ctx);
WorldDataScopeRegistry.getInstance().saveAllScopes(json, ctx, retainedScopeData);
state = SaverState.IDLE;

File fileToWrite = savesFile.beginWrite();
try {
File fileToWrite = savesFile.beginWrite();
ICLog.i("SAVER", "writing saves to " + fileToWrite.getAbsolutePath());
FileUtils.writeJSON(fileToWrite, json);
if (!savesFile.endSuccessfulWrite(true))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package com.zhekasmirnov.innercore.api.runtime.saver.world;

import com.zhekasmirnov.innercore.api.log.ICLog;
import com.zhekasmirnov.innercore.api.mod.ScriptableObjectHelper;
import com.zhekasmirnov.innercore.api.runtime.Callback;
import org.innercore.icstd.serialization.IScopedReader;
import org.innercore.icstd.serialization.IScopedWriter;
import org.innercore.internal.serialization.JsonIO;
import org.innercore.icstd.serialization.SerializationContext;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.javascript.ScriptableObject;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.*;

public class WorldDataScopeRegistry {
private static final WorldDataScopeRegistry instance = new WorldDataScopeRegistry();
Expand Down Expand Up @@ -45,6 +45,19 @@ public interface SaverScope {
void write(IScopedWriter io, SerializationContext ctx);
}

// per-world backup of raw scope data, used to keep the last known copy of a scope in the saves
// file when the scope fails to read or write, instead of dropping or overwriting it with defaults
public static class RetainedScopeData {
private final Map<String, Object> lastKnownData = new HashMap<>();
private final Set<String> failedToRead = new HashSet<>();

public void clear() {
lastKnownData.clear();
failedToRead.clear();
}
}



private final Map<String, SaverScope> scopeMap = new HashMap<>();

Expand All @@ -53,20 +66,30 @@ public void addScope(String name, SaverScope scope) {
return;
}
while (scopeMap.containsKey(name)) {
name += name.hashCode() & 0xFF;
String renamed = name + (name.hashCode() & 0xFF);
ICLog.e("SAVER", "saves scope name collision: '" + name + "' is already registered, new scope will be registered as '" + renamed + "' and its data will depend on registration order");
name = renamed;
}
scopeMap.put(name, scope);
}

public void readAllScopes(JSONObject json, SerializationContext ctx, Map<String, Object> outMissingScopes) {
public void readAllScopes(JSONObject json, SerializationContext ctx, RetainedScopeData retained, Map<String, Object> outMissingScopes) {
for (Map.Entry<String, SaverScope> entry : scopeMap.entrySet()) {
String key = entry.getKey();
Object data = json.opt(key);
if (data != null) {
retained.lastKnownData.put(key, data);
}
JsonIO.ScopedReader reader = null;
try {
reader = new JsonIO.ScopedReader(data);
entry.getValue().read(reader, ctx);
} catch (Throwable err) {
// a scope is marked as failed only when it had data to lose - a reader failing on
// empty data must not suppress saving fresh data
if (data != null) {
retained.failedToRead.add(key);
}
ctx.addError(err, data, "global saves scope failed to read: " + key);
}
if (reader != null) {
Expand All @@ -82,16 +105,48 @@ public void readAllScopes(JSONObject json, SerializationContext ctx, Map<String,
}
}

public void saveAllScopes(JSONObject json, SerializationContext ctx) {
private static void putJsonSafe(JSONObject json, String key, Object data) {
try {
json.put(key, data);
} catch (JSONException ignore) {
}
}

public void saveAllScopes(JSONObject json, SerializationContext ctx, RetainedScopeData retained) {
for (Map.Entry<String, SaverScope> entry : scopeMap.entrySet()) {
String key = entry.getKey();
if (retained.failedToRead.contains(key)) {
// the scope never received its saved data, writing it would replace the data with
// defaults - keep the last known copy instead
Object lastKnown = retained.lastKnownData.get(key);
if (lastKnown != null) {
ICLog.i("SAVER", "scope " + key + " failed to read on world load, keeping its previous data");
putJsonSafe(json, key, lastKnown);
}
continue;
}
try {
JsonIO.ScopedWriter writer = new JsonIO.ScopedWriter();
entry.getValue().write(writer, ctx);
writer.forceEndScope(ctx);
json.put(entry.getKey(), writer.getResult());
Object result = writer.getResult();
putJsonSafe(json, key, result);
if (result != null) {
retained.lastKnownData.put(key, result);
} else {
retained.lastKnownData.remove(key);
}
} catch (Throwable err) {
ctx.addError(err, null, "global saves scope failed to read: " + entry.getKey());
ctx.addError(err, null, "global saves scope failed to write: " + key);
// don't lose the scope - write the data from the last successful read or write
Object lastKnown = retained.lastKnownData.get(key);
if (lastKnown != null) {
putJsonSafe(json, key, lastKnown);
}
}
}
}

}
Loading
Loading