mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-10-05 04:40:25 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
beaa1da276 | ||
|
|
a378c35fb6 | ||
|
|
69ed90bb9d | ||
|
|
e337452d46 | ||
|
|
9f70386466 | ||
|
|
e7de4e1a10 | ||
|
|
4cc15b84f6 | ||
|
|
f3bfa418a5 | ||
|
|
663b0b8737 | ||
|
|
3a54813513 | ||
|
|
3e43d88374 | ||
|
|
580f8f5217 |
@@ -1,6 +1,5 @@
|
||||
If your pull request is **not** translation-related, read the list of requirements below and check each box:
|
||||
|
||||
- [ ] I have read the [contribution guidelines](https://github.com/Anuken/Mindustry/blob/master/CONTRIBUTING.md).
|
||||
- [ ] I have ensured that my code compiles, if applicable.
|
||||
- [ ] I have ensured that any new features in this PR function correctly in-game, if applicable.
|
||||
- [ ] I affirm that this code was not written using generative AI/LLMs.
|
||||
- [ ] I have ensured that any new features or fixes in this PR function correctly in-game, if applicable.
|
||||
- [ ] I affirm that this code follows Mindustry's [AI policy](https://github.com/Anuken/Mindustry/blob/master/CONTRIBUTING.md#ai-policy).
|
||||
+17
-7
@@ -4,9 +4,6 @@ This is for code contributions. For translations, see [TRANSLATING](TRANSLATING.
|
||||
|
||||
## Basic Guidelines
|
||||
|
||||
### Use an IDE.
|
||||
Specifically, IntelliJ IDEA. Download the (free) Community Edition of it [here](https://www.jetbrains.com/idea/download/). Some people use other tools, like VS Code, but I would personally not recommend them for Java development.
|
||||
|
||||
### Always test your changes.
|
||||
Do not submit something without at least running the game to see if it compiles.
|
||||
If you are submitting a new block, make sure it has a name and description, and that it works correctly in-game. If you are changing existing block mechanics, test them out first.
|
||||
@@ -20,11 +17,19 @@ Yes, there are occurrences of trailing spaces, extra newlines, empty indents, an
|
||||
|
||||
I **especially** do not want to see PRs that apply any kind of automated analysis to the source code to "optimize" anything - my IDE can do that already. If the PR doesn't actually change anything useful, I'm not going to review or merge it.
|
||||
|
||||
### Do not make AI "contributions".
|
||||
## AI Policy
|
||||
|
||||
If I see a PR with code that's obviously written by AI, I will reject your PR, and you will be blocked. Don't waste my time with slop.
|
||||
Mindustry's AI contribution policies are similar to that of [the Rust language](https://forge.rust-lang.org/policies/llm-usage.html), which are summarized as follows:
|
||||
|
||||
Asking AI questions, and using that information to help you write code? Fine. Using it to actually write code? No.
|
||||
- ✔️ You **are** allowed use AI to review your own code, summarize, inform, and answer questions.
|
||||
- ✔️ You **are** allowed to use AI to translate your *human-written* messages into English, if necessary.
|
||||
|
||||
- ❌ You are **not** allowed to submit PRs that are generated by AI.
|
||||
- ❌ You are **not** allowed to use AI to *generate* any text posted (PRs, issues, commit messages, etc.)
|
||||
|
||||
- ⚠️ If you post *anything* that is based on AI output (potential solution to a bug, crash diagnosis...), it must be clearly marked as being sourced from AI.
|
||||
|
||||
Repeated (or particularly egregious) violations of these rules will get you banned from this repository.
|
||||
|
||||
## Style Guidelines
|
||||
|
||||
@@ -80,4 +85,9 @@ If something needs to be encapsulated in the future, IntelliJ can handle it with
|
||||
Unless a block of code is very large or used in more than 1-2 places, don't split it up into a separate method. Making unnecessary methods only creates confusion, and may slightly decrease performance.
|
||||
|
||||
## Other Notes
|
||||
If you would like your name to appear in the game's credits, add it to the [list of contributors](https://github.com/Anuken/Mindustry/blob/master/core/assets/contributors) as part of your PR.
|
||||
|
||||
- If you would like your name to appear in the game's credits, add it to the [list of contributors](https://github.com/Anuken/Mindustry/blob/master/core/assets/contributors) as part of your PR.
|
||||
|
||||
- Using an IDE is highly recommended, but not required.
|
||||
- I use IntelliJ; download it [here](https://www.jetbrains.com/idea/download/).
|
||||
- Some people use other tools, like VS Code, but I haven't used them for Java myself, and cannot guarantee that they will work well for this codebase.
|
||||
@@ -183,7 +183,10 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
|
||||
assets.load(schematics);
|
||||
|
||||
assets.loadRun("contentinit", ContentLoader.class, () -> content.init(), () -> content.load());
|
||||
assets.loadRun("contentinit", ContentLoader.class, () -> content.init(), () -> {
|
||||
content.load();
|
||||
mods.loadModPatches();
|
||||
});
|
||||
assets.loadRun("baseparts", BaseRegistry.class, () -> {}, () -> bases.load());
|
||||
|
||||
Core.assets.load("sprites/schematic-background.png", Texture.class).loaded = t -> t.setWrap(TextureWrap.repeat);
|
||||
|
||||
@@ -8,7 +8,6 @@ import arc.struct.*;
|
||||
import arc.util.TaskQueue;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
@@ -263,7 +262,7 @@ public class Pathfinder implements Runnable{
|
||||
tile.build == null || !solid || tile.block() instanceof CoreBlock ? 0 : Math.min((int)(tile.build.health / 40), 80),
|
||||
tid == 0 && tile.build != null && state.rules.coreCapture ? 255 : tid, //use teamid = 255 when core capture is enabled to mark out derelict structures
|
||||
solid,
|
||||
tile.floor().isLiquid && tile.block() == Blocks.air,
|
||||
tile.floor().isLiquid && !tile.block().solid,
|
||||
tile.legSolid(),
|
||||
nearLiquid,
|
||||
nearGround,
|
||||
|
||||
@@ -26,6 +26,7 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
private PhysicsWorld[] physics;
|
||||
private Seq<PhysicRef> refs = new Seq<>(false, 20, PhysicRef.class);
|
||||
private Seq<Future<?>> futures = new Seq<>(false, layers, Future.class);
|
||||
private Seq<PhysicRef> pendingAdds = new Seq<>(false, 20, PhysicRef.class);
|
||||
|
||||
private static volatile long maxPhysicsTime = 0;
|
||||
|
||||
@@ -44,7 +45,8 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
PhysicRef ref = new PhysicRef(unit, body);
|
||||
refs.add(ref);
|
||||
|
||||
if(ref.lastLayer >= 0) physics[ref.lastLayer].add(body);
|
||||
//defer adds until physics update is done
|
||||
pendingAdds.add(ref);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,6 +70,13 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
PerfCounter.unitPhysicsAsync.add(maxPhysicsTime);
|
||||
maxPhysicsTime = 0;
|
||||
|
||||
//flush newly added entities
|
||||
for(int i = 0; i < pendingAdds.size; i++){
|
||||
PhysicRef ref = pendingAdds.items[i];
|
||||
if(ref.lastLayer >= 0) physics[ref.lastLayer].add(ref.body);
|
||||
}
|
||||
pendingAdds.clear();
|
||||
|
||||
//move entities
|
||||
for(PhysicRef ref : refs){
|
||||
Physicsc entity = ref.entity;
|
||||
|
||||
@@ -23,7 +23,7 @@ public class Effect{
|
||||
|
||||
public static final Seq<Effect> all = new Seq<>();
|
||||
|
||||
private boolean initialized;
|
||||
private transient boolean initialized;
|
||||
|
||||
public final int id;
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ public abstract class LStatement{
|
||||
/** Sanitizes variable input strings from text fields into values that will not break logic parsing. */
|
||||
public static String sanitize(String value){
|
||||
if(value.length() == 0){
|
||||
return "";
|
||||
return "null";
|
||||
}else if(value.length() == 1){
|
||||
if(value.charAt(0) == '"' || value.charAt(0) == ';' || value.charAt(0) == ' ' ||
|
||||
value.charAt(0) == '\n' || value.charAt(0) == '\t' || value.charAt(0) == '#'){
|
||||
|
||||
@@ -78,6 +78,7 @@ public class DataManager{
|
||||
PixmapRegion error = new PixmapRegion(Pixmaps.blankPixmap());
|
||||
UnlockableContent[] currentContent = {null};
|
||||
String[] currentHash = {null};
|
||||
Fi serverGeneratedDir = Vars.dataDirectory.child("assets/sprites/generated");
|
||||
|
||||
MultiPacker saver = new MultiPacker(false){
|
||||
@Override
|
||||
@@ -89,10 +90,17 @@ public class DataManager{
|
||||
if(name.startsWith(DataImagePacker.regionPrefix)) name = name.substring(DataImagePacker.regionPrefix.length());
|
||||
String path = "generated/" + currentHash[0] + "/" + name + ".png";
|
||||
ImageAsset newImage = new ImageAsset();
|
||||
newImage.setPath(path);
|
||||
//it would be nice to do this async, but the pixmap typically gets disposed right after add() exist
|
||||
|
||||
//it would be nice to do this async, but the pixmap typically gets disposed right after add()
|
||||
byte[] bytes = PixmapIO.writePngBytes(region.pixmap);
|
||||
newImage.updateData(bytes);
|
||||
if(Vars.headless){ //TODO: doesn't actually work on the server yet
|
||||
Fi file = serverGeneratedDir.child(name + ".png");
|
||||
file.writeBytes(bytes);
|
||||
newImage.readOverride(path, file);
|
||||
}else{
|
||||
newImage.setPath(path);
|
||||
newImage.updateData(bytes);
|
||||
}
|
||||
|
||||
images.add(newImage);
|
||||
packed[0] ++;
|
||||
@@ -150,17 +158,15 @@ public class DataManager{
|
||||
imagePixmaps.each((key, val) -> val.pixmap.dispose());
|
||||
error.pixmap.dispose();
|
||||
|
||||
if(packed[0] > 0 || forcePack){
|
||||
if(!Vars.headless && (packed[0] > 0 || forcePack)){
|
||||
reloadImages();
|
||||
|
||||
if(!Vars.headless){
|
||||
for(var cont : contentToPack){
|
||||
try{
|
||||
cont.loadIcon();
|
||||
cont.load();
|
||||
}catch(Exception e){
|
||||
Log.err("Failed to load icons for " + cont, e);
|
||||
}
|
||||
for(var cont : contentToPack){
|
||||
try{
|
||||
cont.loadIcon();
|
||||
cont.load();
|
||||
}catch(Exception e){
|
||||
Log.err("Failed to load icons for " + cont, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.graphics.MultiPacker.*;
|
||||
import mindustry.mod.ContentParser.*;
|
||||
import mindustry.mod.data.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
@@ -936,6 +937,24 @@ public class Mods implements Loadable{
|
||||
Events.fire(new ModContentLoadEvent());
|
||||
}
|
||||
|
||||
public void loadModPatches(){
|
||||
for(LoadedMod mod : orderedMods()){
|
||||
if(mod.root.child("patches").exists()){
|
||||
try{
|
||||
Seq<Fi> patches = mod.root.child("patches").findAll(f -> f.extEquals("json") || f.extEquals("hjson"));
|
||||
if(patches.size > 0){
|
||||
//create a new patcher per mod so the fields don't get reset
|
||||
DataPatcher patcher = new DataPatcher();
|
||||
patches.sort();
|
||||
patcher.apply(patches.map(f -> new PatchAsset(f.readString())), new Seq<>());
|
||||
}
|
||||
}catch(Exception e){
|
||||
Log.err("Failed to apply patches from mod " + mod.meta.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleContentError(Content content, Throwable error){
|
||||
parser.markError(content, error);
|
||||
}
|
||||
|
||||
@@ -563,7 +563,11 @@ public class SettingsMenuDialog extends BaseDialog{
|
||||
Core.settings.put("swapdiagonal", false);
|
||||
}
|
||||
|
||||
dev.checkPref("console", false);
|
||||
dev.checkPref("console", false, val -> {
|
||||
if(val == false && ui.consolefrag != null){
|
||||
ui.consolefrag.close();
|
||||
}
|
||||
});
|
||||
dev.checkPref("drawhitboxes", false);
|
||||
dev.checkPref("showperformance", false);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ConsoleFragment extends Table{
|
||||
font = Fonts.def;
|
||||
|
||||
visible(() -> {
|
||||
if(input.keyTap(Binding.console) && settings.getBool("console") && (scene.getKeyboardFocus() == chatfield || !(scene.getKeyboardFocus() instanceof TextField)) && !ui.chatfrag.shown()){
|
||||
if(input.keyTap(Binding.console) && (settings.getBool("console") || shown) && (scene.getKeyboardFocus() == chatfield || !(scene.getKeyboardFocus() instanceof TextField)) && !ui.chatfrag.shown()){
|
||||
shown = !shown;
|
||||
if(shown && !open && settings.getBool("console")){
|
||||
toggle();
|
||||
@@ -227,6 +227,11 @@ public class ConsoleFragment extends Table{
|
||||
open = false; //never true on mobile
|
||||
}
|
||||
|
||||
public void close(){
|
||||
shown = false;
|
||||
open = false;
|
||||
}
|
||||
|
||||
public void toggle(){
|
||||
|
||||
if(!open){
|
||||
|
||||
@@ -162,7 +162,7 @@ public class DirectionalUnloader extends Block{
|
||||
public void read(Reads read, byte revision){
|
||||
super.read(read, revision);
|
||||
int id = read.s();
|
||||
unloadItem = id == -1 ? null : content.items().get(id);
|
||||
unloadItem = id == -1 ? null : content.item(id);
|
||||
offset = read.s();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,4 +26,4 @@ org.gradle.caching=true
|
||||
org.gradle.internal.http.socketTimeout=100000
|
||||
org.gradle.internal.http.connectionTimeout=100000
|
||||
android.enableR8.fullMode=false
|
||||
archash=8eb00ffff0
|
||||
archash=7445105cd2
|
||||
|
||||
@@ -54,6 +54,7 @@ public class ServerLauncher implements ApplicationListener{
|
||||
mods.loadScripts();
|
||||
content.createModContent();
|
||||
content.init();
|
||||
mods.loadModPatches();
|
||||
|
||||
if(mods.hasContentErrors()){
|
||||
err("Error occurred loading mod content:");
|
||||
|
||||
@@ -123,7 +123,7 @@ public class LogicTests{
|
||||
Arguments.of("a bare single semicolon is invalid", ";", "invalid"),
|
||||
Arguments.of("a bare single space is invalid", " ", "invalid"),
|
||||
Arguments.of("a single ordinary character passes through unchanged", "a", "a"),
|
||||
Arguments.of("empty input stays empty", "", ""),
|
||||
Arguments.of("empty input gets converted to null", "", "null"),
|
||||
Arguments.of("a plain already-quoted value is untouched", "\"hello\"", "\"hello\""),
|
||||
Arguments.of("a unescaped quote at the end gets doubled", "\"hello\\\"", "\"hello\\\\\""),
|
||||
Arguments.of(
|
||||
@@ -454,4 +454,4 @@ public class LogicTests{
|
||||
void crlfLabelsResolveToTheSameJumpLocationAsLfLabels(){
|
||||
assertDoesNotThrow(() -> load("loop:\r\njump loop always\r\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,12 @@ public class SchemaGenerator{
|
||||
|
||||
var root = getTypeDecl(field.getDeclaringClass());
|
||||
if(root != null) root.getFieldByName(field.getName()).ifPresent(fdec -> {
|
||||
String[] docAndDefault = determineJavadocAndDefault(field, fdec, fdec.getVariables().getFirst().orElseThrow());
|
||||
var variable = fdec.getVariables().stream()
|
||||
.filter(v -> v.getNameAsString().equals(field.getName()))
|
||||
.findFirst().orElse(null);
|
||||
if(variable == null) return;
|
||||
|
||||
String[] docAndDefault = determineJavadocAndDefault(field, fdec, variable);
|
||||
if(docAndDefault[0] != null) inner.put("doc", docAndDefault[0]);
|
||||
if(docAndDefault[1] != null) inner.put("default", docAndDefault[1]);
|
||||
});
|
||||
@@ -302,8 +307,14 @@ public class SchemaGenerator{
|
||||
initValue = "[]";
|
||||
}
|
||||
|
||||
//field
|
||||
if(initValue.contains(".") && !(baseField.getType().isArray())){
|
||||
//numeric literal: drop the Java-only suffix (f/F/d/D/L), keep the decimal point for JSON
|
||||
boolean numeric = initValue.matches("-?(\\d+\\.?\\d*|\\.\\d+)([eE][-+]?\\d+)?[fFdDlL]?");
|
||||
if(numeric){
|
||||
initValue = initValue.replaceAll("[fFdDlL]$", "");
|
||||
}
|
||||
|
||||
//field reference, e.g. Category.turret -> turret (never applied to numbers)
|
||||
if(!numeric && initValue.contains(".") && !(baseField.getType().isArray())){
|
||||
var split = initValue.split("\\.");
|
||||
initValue = split[split.length - 1];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user