Compare commits

...

8 Commits

Author SHA1 Message Date
Jonathan Coates 5c457950d8
Merge branch 'mc-1.20.x' into mc-1.20.y 2024-04-24 20:30:59 +01:00
Jonathan Coates 925092add3
Fix build on Windows
- Force encoding to UTF-8
 - Fix npm not being found on the path
 - Test building common/web on OSX and Windows
2024-04-24 17:55:48 +01:00
Jonathan Coates 550296edc5
Fix typo in speaker docs
Closes #1799
2024-04-21 09:45:02 +01:00
Jonathan Coates 0771c4891b
Various Gradle tweaks
- Update Gradle to 8.7
 - Configure IntelliJ to build internally, rather than delgating to
   Gradle. We've seen some weird issues with using delegated builds, so
   best avoided.
 - Remove gitpod config. This has been broken for a while (used Java 16
   rather than 17) and nobody noticed, so I suspect nobody uses this.
2024-04-19 18:14:51 +01:00
Jonathan Coates 776fa00b94
Update to latest TeaVM
- Add the core TeaVM jar to the runtime the classpath, to ensure
   various runtime classes are present.
 - Fix computer initialisation errors not being displayed on the screen.
   The terminal was set to the default 0x0 size when logging the error,
   and so never displayed anything!
2024-04-17 21:57:11 +01:00
Jonathan Coates 03bb279206
Move computer right click code to the block
Rather than handling right clicks within the block entity code, we now
handle it within the block. Turtles now handle the nametagging
behaviour themselves, rather than overriding canNameWithTag.
2024-04-17 15:01:50 +01:00
Weblate fabd77132d Translations for Czech
Co-authored-by: Patriik <apatriik0@gmail.com>
2024-04-09 08:54:05 +00:00
Jonathan Coates 95be0a25bf
Update Cobalt to 0.9.3
- Fix some errors missing source positions
 - Poll interrupted state when parsing Lua
2024-04-08 12:18:21 +01:00
22 changed files with 140 additions and 121 deletions

View File

@ -58,13 +58,13 @@ jobs:
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
- name: 📤 Upload Jar
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CC-Tweaked
path: ./jars
- name: 📤 Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
build-core:
strategy:
@ -81,24 +81,28 @@ jobs:
runs-on: ${{ matrix.uses }}
steps:
- name: Clone repository
- name: 📥 Clone repository
uses: actions/checkout@v4
- name: Set up Java
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Setup Gradle
- name: 📥 Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
- name: Run tests
- name: ⚒️ Build
run: |
./gradlew --configure-on-demand :core:assemble :web:assemble
- name: 🧪 Run tests
run: |
./gradlew --configure-on-demand :core:test
- name: Parse test reports
- name: 🧪 Parse test reports
run: python3 ./tools/parse-reports.py
if: ${{ failure() }}

View File

@ -1,26 +0,0 @@
# SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
#
# SPDX-License-Identifier: MPL-2.0
image:
file: config/gitpod/Dockerfile
ports:
- port: 25565
onOpen: notify
vscode:
extensions:
- eamodio.gitlens
- github.vscode-pull-request-github
- ms-azuretools.vscode-docker
- redhat.java
- richardwillis.vscode-gradle
- vscjava.vscode-java-debug
- vscode.github
tasks:
- name: Setup pre-commit hool
init: pre-commit install --allow-missing-config
- name: Install npm packages
init: npm ci

View File

@ -29,9 +29,9 @@ ## Setting up a development environment
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
- Make sure you've got the following software installed:
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
- Java Development Kit (JDK). This can be downloaded from [Adoptium].
- [Git](https://git-scm.com/).
- If you want to work on documentation, [NodeJS][node].
- [NodeJS][node].
- Download CC: Tweaked's source code:
```

View File

@ -5,9 +5,8 @@
import cc.tweaked.gradle.JUnitExt
import net.fabricmc.loom.api.LoomGradleExtensionAPI
import net.fabricmc.loom.util.gradle.SourceSetHelper
import org.jetbrains.gradle.ext.compiler
import org.jetbrains.gradle.ext.runConfigurations
import org.jetbrains.gradle.ext.settings
import org.jetbrains.gradle.ext.*
import org.jetbrains.gradle.ext.Application
plugins {
publishing
@ -86,6 +85,19 @@ idea.project.settings.runConfigurations {
moduleName = "${idea.project.name}.forge.test"
packageName = ""
}
register<Application>("Standalone") {
moduleName = "${idea.project.name}.standalone.main"
mainClass = "cc.tweaked.standalone.Main"
programParameters = "--resources=projects/core/src/main/resources --term=80x30 --allow-local-domains"
}
}
// Build with the IntelliJ, rather than through Gradle. This may require setting the "Compiler Output" option in
// "Project Structure".
idea.project.settings.delegateActions {
delegateBuildRunToGradle = false
testRunner = ActionDelegationConfig.TestRunner.PLATFORM
}
idea.project.settings.compiler.javac {

View File

@ -46,7 +46,7 @@ abstract class NpmInstall : DefaultTask() {
@TaskAction
fun install() {
project.exec {
commandLine("npm", "ci")
commandLine(ProcessHelpers.getExecutable("npm"), "ci")
workingDir = projectRoot.get().asFile
}
}
@ -59,6 +59,6 @@ fun install() {
abstract class NpxExecToDir : ExecToDir() {
init {
dependsOn(NpmInstall.TASK_NAME)
executable = "npx"
executable = ProcessHelpers.getExecutable("npx")
}
}

View File

@ -9,6 +9,7 @@
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
internal object ProcessHelpers {
fun startProcess(vararg command: String): Process {
@ -34,7 +35,7 @@ fun captureLines(vararg command: String): List<String> {
val process = startProcess(*command)
process.outputStream.close()
val out = BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
val out = BufferedReader(InputStreamReader(process.inputStream, StandardCharsets.UTF_8)).use { reader ->
reader.lines().filter { it.isNotEmpty() }.toList()
}
ProcessGroovyMethods.closeStreams(process)
@ -46,6 +47,28 @@ fun onPath(name: String): Boolean {
val path = System.getenv("PATH") ?: return false
return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() }
}
/**
* Search for an executable on the `PATH` if required.
*
* [Process]/[ProcessBuilder] does not handle all executable file extensions on Windows (such as `.com). When on
* Windows, this function searches `PATH` and `PATHEXT` for an executable matching [name].
*/
fun getExecutable(name: String): String {
if (!System.getProperty("os.name").lowercase().contains("windows")) return name
val path = (System.getenv("PATH") ?: return name).split(File.pathSeparator)
val pathExt = (System.getenv("PATHEXT") ?: return name).split(File.pathSeparator)
for (pathEntry in path) {
for (ext in pathExt) {
val resolved = File(pathEntry, name + ext)
if (resolved.exists()) return resolved.getAbsolutePath()
}
}
return name
}
}
internal fun Process.waitForOrThrow(message: String) {

View File

@ -1,12 +0,0 @@
# SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
#
# SPDX-License-Identifier: MPL-2.0
FROM gitpod/workspace-base
USER gitpod
RUN sudo apt-get -q update \
&& sudo apt-get install -yq openjdk-16-jdk python3-pip npm \
&& sudo pip3 install pre-commit \
&& sudo update-java-alternatives --set java-1.16.0-openjdk-amd64

View File

@ -131,7 +131,7 @@ ## Storing audio
First, we require the dfpwm module and call [`cc.audio.dfpwm.make_decoder`] to construct a new decoder. This decoder
accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, which we can then play with our speaker.
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPWM uses a single bit for each
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
[`io.lines`], which provides a nice way to loop over chunks of a file. You can of course just use [`fs.open`] and
[`fs.ReadHandle.read`] if you prefer.

View File

@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: MPL-2.0
org.gradle.jvmargs=-Xmx3G
org.gradle.jvmargs=-Xmx3G -Dfile.encoding=UTF-8
org.gradle.parallel=true
kotlin.stdlib.default.dependency=false

View File

@ -26,7 +26,7 @@ slf4j = "2.0.7"
asm = "9.6"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = "0.9.2"
cobalt = "0.9.3"
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
@ -59,7 +59,7 @@ checkstyle = "10.14.1"
curseForgeGradle = "1.1.18"
errorProne-core = "2.23.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.5.7"
fabric-loom = "1.6.7"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
@ -70,8 +70,8 @@ neoGradle = "7.0.100"
nullAway = "0.10.25"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.10.0-SQUID.3"
vanillaExtract = "0.1.2"
teavm = "0.10.0-SQUID.4"
vanillaExtract = "0.1.3"
versionCatalogUpdate = "0.8.1"
[libraries]
@ -152,6 +152,7 @@ neoGradle-userdev = { module = "net.neoforged.gradle:userdev", version.ref = "ne
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
teavm-core = { module = "org.teavm:teavm-core", version.ref = "teavm" }
teavm-jso = { module = "org.teavm:teavm-jso", version.ref = "teavm" }
teavm-jso-apis = { module = "org.teavm:teavm-jso-apis", version.ref = "teavm" }
teavm-jso-impl = { module = "org.teavm:teavm-jso-impl", version.ref = "teavm" }

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -8,6 +8,7 @@
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import net.minecraft.core.BlockPos;
@ -160,8 +161,19 @@ public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable L
@Override
@Deprecated
public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
return world.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer ? computer.use(player, hand) : InteractionResult.PASS;
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (!player.isCrouching() && level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer) {
// Regular right click to activate computer
if (!level.isClientSide && computer.isUsable(player)) {
var serverComputer = computer.createServerComputer();
serverComputer.turnOn();
new ComputerContainerData(serverComputer, getItem(computer)).open(player, computer);
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
return super.use(state, level, pos, player, hand, hit);
}
@Override

View File

@ -13,7 +13,6 @@
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.BlockEntityHelpers;
@ -25,10 +24,10 @@
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.*;
import net.minecraft.world.LockCode;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.Nameable;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
@ -76,10 +75,6 @@ public void setRemoved() {
unload();
}
protected boolean canNameWithTag(Player player) {
return false;
}
protected double getInteractRange() {
return BlockEntityHelpers.DEFAULT_INTERACT_RANGE;
}
@ -89,31 +84,6 @@ public boolean isUsable(Player player) {
&& BlockEntityHelpers.isUsable(this, player, getInteractRange());
}
public InteractionResult use(Player player, InteractionHand hand) {
var currentItem = player.getItemInHand(hand);
if (!currentItem.isEmpty() && currentItem.getItem() == Items.NAME_TAG && canNameWithTag(player) && currentItem.hasCustomHoverName()) {
// Label to rename computer
if (!getLevel().isClientSide) {
setLabel(currentItem.getHoverName().getString());
currentItem.shrink(1);
}
return InteractionResult.sidedSuccess(getLevel().isClientSide);
} else if (!player.isCrouching()) {
// Regular right click to activate computer
if (!getLevel().isClientSide && isUsable(player)) {
var computer = createServerComputer();
computer.turnOn();
var stack = getBlockState().getBlock() instanceof AbstractComputerBlock<?>
? ((AbstractComputerBlock<?>) getBlockState().getBlock()).getItem(this)
: ItemStack.EMPTY;
new ComputerContainerData(computer, stack).open(player, this);
}
return InteractionResult.sidedSuccess(getLevel().isClientSide);
}
return InteractionResult.PASS;
}
protected void serverTick() {
if (getLevel().isClientSide) return;
if (computerID < 0 && !startOn) return; // Don't tick if we don't need a computer!
@ -343,7 +313,7 @@ public final void setComputerID(int id) {
if (getLevel().isClientSide || computerID == id) return;
computerID = id;
setChanged();
BlockEntityHelpers.updateBlock(this);
}
@Override
@ -353,7 +323,7 @@ public final void setLabel(@Nullable String label) {
this.label = label;
var computer = getServerComputer();
if (computer != null) computer.setLabel(label);
setChanged();
BlockEntityHelpers.updateBlock(this);
}
@Override

View File

@ -21,10 +21,13 @@
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.Containers;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Explosion;
@ -41,6 +44,7 @@
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
@ -168,6 +172,22 @@ public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable L
}
}
@Override
@Deprecated
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
var currentItem = player.getItemInHand(hand);
if (currentItem.getItem() == Items.NAME_TAG && currentItem.hasCustomHoverName() && level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer) {
// Label to rename computer
if (!level.isClientSide) {
computer.setLabel(currentItem.getHoverName().getString());
currentItem.shrink(1);
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
return super.use(state, level, pos, player, hand, hit);
}
@ForgeOverride
public float getExplosionResistance(BlockState state, BlockGetter world, BlockPos pos, Explosion explosion) {
var exploder = explosion.getDirectSourceEntity();

View File

@ -87,11 +87,6 @@ protected void unload() {
if (!hasMoved()) super.unload();
}
@Override
protected boolean canNameWithTag(Player player) {
return true;
}
@Override
protected double getInteractRange() {
return 12.0;

View File

@ -136,7 +136,7 @@
"gui.computercraft.config.term_sizes": "Velikosti terminálu",
"gui.computercraft.config.term_sizes.computer": "Počítač",
"gui.computercraft.config.term_sizes.computer.height": "Výška terminálu",
"gui.computercraft.config.term_sizes.computer.height.tooltip": "Rozsash: 1 ~ 255",
"gui.computercraft.config.term_sizes.computer.height.tooltip": "Rozsah: 1 ~ 255",
"gui.computercraft.config.term_sizes.computer.tooltip": "Velikost počítačového terminálu.",
"gui.computercraft.config.term_sizes.computer.width": "Šířka terminálu",
"gui.computercraft.config.term_sizes.computer.width.tooltip": "Rozsah: 1 ~ 255",
@ -177,7 +177,7 @@
"gui.computercraft.tooltip.turn_off.key": "Zmáčkni Ctrl+S",
"gui.computercraft.tooltip.turn_on": "Zapnout teno počítač",
"gui.computercraft.upload.failed": "Nahrávání se nezdařilo",
"gui.computercraft.upload.failed.computer_off": "Před nahráním souborů musíš vypnout počítač.",
"gui.computercraft.upload.failed.computer_off": "Před nahráním souborů musíš zapnout počítač.",
"gui.computercraft.upload.failed.corrupted": "Soubory byly porušeny při nahrávání. Prosím zkus to znovu.",
"gui.computercraft.upload.failed.generic": "Nahrávání souborů se nezdařilo (%s)",
"gui.computercraft.upload.failed.name_too_long": "Jména souborů pro nahrání jsou příliš dlouhá.",
@ -216,5 +216,11 @@
"upgrade.minecraft.diamond_hoe.adjective": "Farmářský",
"upgrade.minecraft.diamond_pickaxe.adjective": "Těžební",
"upgrade.minecraft.diamond_shovel.adjective": "Kopací",
"upgrade.minecraft.diamond_sword.adjective": "Bojový"
"upgrade.minecraft.diamond_sword.adjective": "Bojový",
"tag.item.computercraft.computer": "Počítače",
"tag.item.computercraft.monitor": "Monitory",
"tag.item.computercraft.turtle": "Roboti",
"tag.item.computercraft.wired_modem": "Drátové modemy",
"gui.computercraft.config.http.proxy": "Proxy",
"gui.computercraft.config.upload_max_size.tooltip": "Limit velikosti nahrávaného souboru v bytech. Musí být v rozmezí od 1 KiB do 16 MiB.\nMěj na paměti, že nahrávání souborů je zpracováváno v jednom ticku - velké soubory nebo\nšpatný výkon sítě mohou zpomalit síťové vlákno. A nezapomeň na místo na disku!\nRozsah: 1024 ~ 16777216"
}

View File

@ -17,7 +17,6 @@
import net.minecraft.core.Direction
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.InteractionHand
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.Blocks
@ -140,8 +139,7 @@ fun Open_on_client(context: GameTestHelper) = context.sequence {
// Teleport the player to the computer and then open it.
thenExecute {
context.positionAt(BlockPos(2, 2, 1))
val computer = context.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.COMPUTER_ADVANCED.get())
computer.use(context.level.randomPlayer!!, InteractionHand.MAIN_HAND)
context.useBlock(BlockPos(2, 2, 2), context.level.randomPlayer!!)
}
// Assert the terminal is synced to the client.
thenIdle(2)

View File

@ -183,7 +183,7 @@ function unset(name)
end
--- Resets the value of all settings. Equivalent to calling [`settings.unset`]
--- on every setting.
-- on every setting.
--
-- @see settings.unset
function clear()
@ -213,16 +213,16 @@ end
-- Existing settings will be merged with any pre-existing ones. Conflicting
-- entries will be overwritten, but any others will be preserved.
--
-- @tparam[opt] string sPath The file to load from, defaulting to `.settings`.
-- @tparam[opt=".settings"] string path The file to load from.
-- @treturn boolean Whether settings were successfully read from this
-- file. Reasons for failure may include the file not existing or being
-- corrupted.
--
-- @see settings.save
-- @changed 1.87.0 `sPath` is now optional.
function load(sPath)
expect(1, sPath, "string", "nil")
local file = fs.open(sPath or ".settings", "r")
-- @changed 1.87.0 `path` is now optional.
function load(path)
expect(1, path, "string", "nil")
local file = fs.open(path or ".settings", "r")
if not file then
return false
end
@ -255,14 +255,14 @@ end
-- This will entirely overwrite the pre-existing file. Settings defined in the
-- file, but not currently loaded will be removed.
--
-- @tparam[opt] string sPath The path to save settings to, defaulting to `.settings`.
-- @tparam[opt=".settings"] string path The path to save settings to.
-- @treturn boolean If the settings were successfully saved.
--
-- @see settings.load
-- @changed 1.87.0 `sPath` is now optional.
function save(sPath)
expect(1, sPath, "string", "nil")
local file = fs.open(sPath or ".settings", "w")
-- @changed 1.87.0 `path` is now optional.
function save(path)
expect(1, path, "string", "nil")
local file = fs.open(path or ".settings", "w")
if not file then
return false
end

View File

@ -29,6 +29,7 @@ dependencies {
implementation(libs.guava)
implementation(libs.netty.http)
implementation(libs.slf4j)
runtimeOnly(libs.teavm.core) // Contains the TeaVM runtime
"builderCompileOnly"(libs.bundles.annotations)
"builderImplementation"(libs.bundles.teavm.tooling)

View File

@ -68,6 +68,10 @@ public void remapClass(String from, String to) {
}
private @Nullable Path findUnmappedFile(String name) {
// For some odd reason, we try to resolve the generated lambda classes. This includes classes called
// things like <linit>lambda, which is an invalid path on Windows. Detect those, and abort early.
if (name.indexOf("<") >= 0) return null;
return classpath.stream().map(x -> x.resolve(name)).filter(Files::exists).findFirst().orElse(null);
}
@ -95,13 +99,18 @@ protected Class<?> findClass(String name) throws ClassNotFoundException {
@Override
public @Nullable InputStream getResourceAsStream(String name) {
if (!name.endsWith(".class")) return super.getResourceAsStream(name);
if (!name.endsWith(".class") || name.startsWith("java/") || name.startsWith("javax/")) {
return super.getResourceAsStream(name);
}
var lastFile = this.lastFile;
if (lastFile != null && lastFile.name().equals(name)) return new ByteArrayInputStream(lastFile.contents());
var path = findFile(name);
if (path == null) return null;
if (path == null) {
System.out.printf("Cannot find %s. Falling back to system class loader.\n", name);
return super.getResourceAsStream(name);
}
ClassReader reader;
try (var stream = Files.newInputStream(path)) {

View File

@ -72,6 +72,10 @@ class EmulatedComputer implements ComputerDisplay, ComputerActionable {
for (const callback of this.callbacks) callback(computer);
})
.catch(e => {
console.error(e);
if (this.terminal.sizeX === 0 || this.terminal.sizeY === 0) this.terminal.resize(51, 19);
const width = this.terminal.sizeX;
const fg = "0".repeat(width);
const bg = "e".repeat(width);
@ -83,6 +87,8 @@ class EmulatedComputer implements ComputerDisplay, ComputerActionable {
this.terminal.fore[y] = fg;
this.terminal.back[y] = bg;
}
this.flushTerminal();
});
}