Initial sanctuary sources

This commit is contained in:
cnstiout
2026-07-04 20:27:08 +02:00
commit c796a20321
1582 changed files with 253264 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
# Gradle
.gradle/
build/
out/
classes/
# Eclipse
*.launch
# IntelliJ IDEA
.idea/
*.iml
*.ipr
*.iws
# VS Code
.settings/
.vscode/
bin/
.classpath
.project
# macOS
*.DS_Store
# Fabric
run/
# Java
.jdk/
hs_err_*.log
replay_*.log
*.hprof
*.jfr
+67
View File
@@ -0,0 +1,67 @@
plugins {
id "net.fabricmc.fabric-loom" version "${loom_version}"
id "maven-publish"
}
version = project.mod_version
group = project.maven_group
repositories {
}
loom {
splitEnvironmentSourceSets()
mods {
"spiderman" {
sourceSet sourceSets.main
sourceSet sourceSets.client
}
}
}
dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}"
implementation "net.fabricmc:fabric-loader:${project.loader_version}"
implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
}
processResources {
def version = project.version
inputs.property "version", version
filesMatching("fabric.mod.json") {
expand "version": version
}
}
tasks.withType(JavaCompile).configureEach {
it.options.release = 25
}
java {
withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
}
jar {
def projectName = project.name
inputs.property "projectName", projectName
from("../../license.txt") {
rename { "${it}_${projectName}" }
}
}
publishing {
publications {
create("mavenJava", MavenPublication) {
from components.java
}
}
repositories {
}
}
+21
View File
@@ -0,0 +1,21 @@
# Done to increase the memory available to Gradle.
org.gradle.jvmargs=-Xmx1G
org.gradle.parallel=true
# IntelliJ IDEA is not yet fully compatible with configuration cache:
# https://github.com/FabricMC/fabric-loom/issues/1349
org.gradle.configuration-cache=false
# Fabric properties
# Check current versions on https://fabricmc.net/develop
minecraft_version=26.1.2
loader_version=0.19.2
loom_version=1.16-SNAPSHOT
# Mod properties
mod_version=1.0.0
maven_group=fr.koka99cab
archives_base_name=spiderman
# Dependencies
fabric_api_version=0.148.0+26.1.2
Binary file not shown.
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+253
View File
@@ -0,0 +1,253 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
if [ -z "$JAVA_HOME" ] && [ -x "$APP_HOME/.jdk/current/bin/java" ] ; then
JAVA_HOME=$APP_HOME/.jdk/current
export JAVA_HOME
fi
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+93
View File
@@ -0,0 +1,93 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+12
View File
@@ -0,0 +1,12 @@
pluginManagement {
repositories {
maven {
name = "Fabric"
url = "https://maven.fabricmc.net/"
}
mavenCentral()
gradlePluginPortal()
}
}
rootProject.name = "spiderman"
@@ -0,0 +1,17 @@
package fr.koka99cab.spiderman.client;
import fr.koka99cab.spiderman.SpidermanMod;
import fr.koka99cab.spiderman.client.item.GrapplingRodCastProperty;
import fr.koka99cab.spiderman.client.render.GrapplingHookRenderer;
import fr.koka99cab.spiderman.mixin.client.ConditionalItemModelPropertiesAccessor;
import fr.koka99cab.spiderman.registry.SpidermanEntityTypes;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry;
public class SpidermanClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ConditionalItemModelPropertiesAccessor.spiderman$getIdMapper().put(SpidermanMod.id("grappling_rod/cast"), GrapplingRodCastProperty.MAP_CODEC);
EntityRendererRegistry.register(SpidermanEntityTypes.GRAPPLING_HOOK, GrapplingHookRenderer::new);
}
}
@@ -0,0 +1,39 @@
package fr.koka99cab.spiderman.client.item;
import com.mojang.serialization.MapCodec;
import fr.koka99cab.spiderman.entity.GrapplingHookEntity;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
public record GrapplingRodCastProperty() implements ConditionalItemModelProperty {
public static final MapCodec<GrapplingRodCastProperty> MAP_CODEC = MapCodec.unit(new GrapplingRodCastProperty());
@Override
public boolean get(ItemStack stack, ClientLevel level, LivingEntity entity, int seed, ItemDisplayContext displayContext) {
if (!(entity instanceof Player player) || !isHeldStack(player, stack)) {
return false;
}
for (Entity renderedEntity : level.entitiesForRendering()) {
if (renderedEntity instanceof GrapplingHookEntity hook && hook.getOwner() == player) {
return true;
}
}
return false;
}
@Override
public MapCodec<GrapplingRodCastProperty> type() {
return MAP_CODEC;
}
private static boolean isHeldStack(Player player, ItemStack stack) {
return player.getMainHandItem() == stack || player.getOffhandItem() == stack;
}
}
@@ -0,0 +1,59 @@
package fr.koka99cab.spiderman.client.render;
import fr.koka99cab.spiderman.entity.GrapplingHookEntity;
import java.util.List;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.ThrownItemRenderer;
import net.minecraft.client.renderer.entity.state.EntityRenderState.LeashState;
import net.minecraft.client.renderer.entity.state.ThrownItemRenderState;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
public class GrapplingHookRenderer extends ThrownItemRenderer<GrapplingHookEntity> {
private static final Vec3 HOOK_LEASH_OFFSET = new Vec3(0.0D, 0.04D, 0.0D);
public GrapplingHookRenderer(EntityRendererProvider.Context context) {
super(context, 0.9F, false);
this.shadowRadius = 0.08F;
}
@Override
public void extractRenderState(GrapplingHookEntity hook, ThrownItemRenderState state, float partialTick) {
super.extractRenderState(hook, state, partialTick);
Entity owner = hook.getOwner();
if (owner == null) {
state.leashStates = null;
return;
}
Vec3 hookPosition = hook.getPosition(partialTick);
Vec3 leashStart = hookPosition.add(HOOK_LEASH_OFFSET);
Vec3 leashEnd = owner.getRopeHoldPosition(partialTick);
LeashState leashState = new LeashState();
leashState.offset = HOOK_LEASH_OFFSET;
leashState.start = leashStart;
leashState.end = leashEnd;
leashState.slack = false;
fillLight(hook.level(), leashState, leashStart, leashEnd);
state.leashStates = List.of(leashState);
}
@Override
protected boolean affectedByCulling(GrapplingHookEntity hook) {
return false;
}
private static void fillLight(Level level, LeashState leashState, Vec3 leashStart, Vec3 leashEnd) {
BlockPos start = BlockPos.containing(leashStart);
BlockPos end = BlockPos.containing(leashEnd);
leashState.startBlockLight = level.getBrightness(LightLayer.BLOCK, start);
leashState.endBlockLight = level.getBrightness(LightLayer.BLOCK, end);
leashState.startSkyLight = level.getBrightness(LightLayer.SKY, start);
leashState.endSkyLight = level.getBrightness(LightLayer.SKY, end);
}
}
@@ -0,0 +1,17 @@
package fr.koka99cab.spiderman.mixin.client;
import com.mojang.serialization.MapCodec;
import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperties;
import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty;
import net.minecraft.resources.Identifier;
import net.minecraft.util.ExtraCodecs;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ConditionalItemModelProperties.class)
public interface ConditionalItemModelPropertiesAccessor {
@Accessor("ID_MAPPER")
static ExtraCodecs.LateBoundIdMapper<Identifier, MapCodec<? extends ConditionalItemModelProperty>> spiderman$getIdMapper() {
throw new AssertionError();
}
}
@@ -0,0 +1,26 @@
package fr.koka99cab.spiderman;
import fr.koka99cab.spiderman.grapple.GrappleController;
import fr.koka99cab.spiderman.registry.SpidermanEntityTypes;
import fr.koka99cab.spiderman.registry.SpidermanItems;
import net.fabricmc.api.ModInitializer;
import net.minecraft.resources.Identifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SpidermanMod implements ModInitializer {
public static final String MOD_ID = "spiderman";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
@Override
public void onInitialize() {
SpidermanEntityTypes.register();
SpidermanItems.register();
GrappleController.register();
LOGGER.info("Spiderman charge: le grappin est pret.");
}
public static Identifier id(String path) {
return Identifier.fromNamespaceAndPath(MOD_ID, path);
}
}
@@ -0,0 +1,121 @@
package fr.koka99cab.spiderman.entity;
import fr.koka99cab.spiderman.grapple.GrappleController;
import fr.koka99cab.spiderman.registry.SpidermanEntityTypes;
import fr.koka99cab.spiderman.registry.SpidermanItems;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.projectile.ItemSupplier;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
public class GrapplingHookEntity extends Projectile implements ItemSupplier {
private static final EntityDataAccessor<Boolean> ANCHORED = SynchedEntityData.defineId(GrapplingHookEntity.class, EntityDataSerializers.BOOLEAN);
private static final double GRAVITY = 0.035D;
private static final double DRAG = 0.985D;
private static final double MAX_FLIGHT_DISTANCE = 72.0D;
private static final int MAX_FLIGHT_TICKS = 80;
private double traveledDistance;
public GrapplingHookEntity(EntityType<? extends GrapplingHookEntity> entityType, Level level) {
super(entityType, level);
this.noPhysics = true;
this.setNoGravity(true);
}
public GrapplingHookEntity(Level level, Entity owner, Vec3 startPosition, Vec3 velocity) {
this(SpidermanEntityTypes.GRAPPLING_HOOK, level);
this.setOwner(owner);
this.snapTo(startPosition.x, startPosition.y, startPosition.z);
this.setDeltaMovement(velocity);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
builder.define(ANCHORED, false);
}
@Override
public void tick() {
super.tick();
this.noPhysics = true;
this.setNoGravity(true);
if (!this.level().isClientSide() && (this.getOwner() == null || !this.getOwner().isAlive())) {
this.discard();
return;
}
if (this.isAnchored()) {
this.setDeltaMovement(Vec3.ZERO);
return;
}
Vec3 velocity = this.getDeltaMovement();
Vec3 from = this.position();
Vec3 to = from.add(velocity);
BlockHitResult hitResult = this.level().clip(new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this));
if (hitResult.getType() == HitResult.Type.BLOCK) {
this.anchor(hitResult);
if (!this.level().isClientSide() && this.getOwner() instanceof ServerPlayer player) {
GrappleController.attach(player, this, hitResult);
}
return;
}
this.move(MoverType.SELF, velocity);
this.traveledDistance += velocity.length();
this.setDeltaMovement(velocity.scale(DRAG).add(0.0D, -GRAVITY, 0.0D));
if (!this.level().isClientSide() && this.getOwner() instanceof ServerPlayer player && (this.traveledDistance > MAX_FLIGHT_DISTANCE || this.tickCount > MAX_FLIGHT_TICKS)) {
GrappleController.expire(player, this);
}
}
public void anchor(BlockHitResult hitResult) {
this.entityData.set(ANCHORED, true);
this.noPhysics = true;
this.setNoGravity(true);
this.setDeltaMovement(Vec3.ZERO);
Vec3 hitLocation = hitResult.getLocation();
this.snapTo(hitLocation.x, hitLocation.y, hitLocation.z);
}
public boolean isAnchored() {
return this.entityData.get(ANCHORED);
}
@Override
public ItemStack getItem() {
return new ItemStack(SpidermanItems.GRAPPLING_HAND);
}
@Override
public boolean isPickable() {
return false;
}
@Override
protected void addAdditionalSaveData(ValueOutput output) {
super.addAdditionalSaveData(output);
}
@Override
protected void readAdditionalSaveData(ValueInput input) {
super.readAdditionalSaveData(input);
}
}
@@ -0,0 +1,427 @@
package fr.koka99cab.spiderman.grapple;
import fr.koka99cab.spiderman.entity.GrapplingHookEntity;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Input;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
public final class GrappleController {
private static final Map<UUID, GrappleSession> ACTIVE = new HashMap<>();
private static final double CAST_SPEED = 2.35D;
private static final double CAST_PLAYER_VELOCITY_FACTOR = 0.9D;
private static final double MAX_INITIAL_HOOK_SPEED = 4.25D;
private static final double CAST_FORWARD_OFFSET = 0.65D;
private static final double CAST_DOWN_OFFSET = 0.18D;
private static final double INITIAL_SLACK = 5.0D;
private static final double HELD_ROPE_SLACK = 0.02D;
private static final double ROPE_TAUT_TOLERANCE = 0.12D;
private static final double MIN_ROPE_LENGTH = 1.0D;
private static final double MAX_EXTRA_ROPE = 64.0D;
private static final double MAX_SESSION_DISTANCE = 152.0D;
private static final double SWING_PUMP = 0.055D;
private static final double REEL_IN_SPEED = 0.18D;
private static final double STUCK_REEL_IN_SPEED = 0.24D;
private static final double STUCK_SPEED_SQ = 0.018D;
private static final int STUCK_REEL_DELAY = 8;
private static final double PENDULUM_GRAVITY = 0.08D;
private static final double SWING_DAMPING = 0.998D;
private static final double FALL_INERTIA_TO_SWING = 1.22D;
private static final double CATCH_LIFT_FROM_SPEED = 0.18D;
private static final double CATCH_LIFT_FROM_FALL = 0.22D;
private static final double MAX_CATCH_LIFT = 0.72D;
private static final double MAX_SWING_SPEED = 6.5D;
private static final double PLAYER_CENTER_FACTOR = 0.5D;
private static final double RELEASE_BOOST_SPEED = 0.85D;
private static final double RELEASE_BOOST_FROM_SWING_SPEED = 0.12D;
private static final double MAX_RELEASE_SPEED_BOOST = 0.65D;
private static final double MAX_RELEASE_SPEED = 8.0D;
private static final double MIN_RELEASE_SWING_SPEED_SQ = 0.05D;
private GrappleController() {
}
public static void register() {
ServerTickEvents.END_SERVER_TICK.register(server -> {
Set<UUID> onlinePlayers = new HashSet<>();
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
onlinePlayers.add(player.getUUID());
tick(player);
}
ACTIVE.keySet().removeIf(uuid -> !onlinePlayers.contains(uuid));
});
}
public static boolean start(ServerLevel level, ServerPlayer player, InteractionHand hand, ItemStack stack) {
GrappleSession existingSession = ACTIVE.get(player.getUUID());
if (existingSession != null) {
stop(player, true);
playRetrieveSound(level, player);
return true;
}
Vec3 look = player.calculateViewVector(player.getXRot(), player.getYRot()).normalize();
Vec3 startPosition = player.getEyePosition().add(look.scale(CAST_FORWARD_OFFSET)).add(0.0D, -CAST_DOWN_OFFSET, 0.0D);
Vec3 velocity = initialHookVelocity(player, look);
GrapplingHookEntity hook = new GrapplingHookEntity(level, player, startPosition, velocity);
level.addFreshEntity(hook);
ACTIVE.put(player.getUUID(), new GrappleSession(level.dimension(), startPosition, BlockPos.containing(startPosition), hook.getId()));
stack.hurtAndBreak(1, player, hand);
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.FISHING_BOBBER_THROW, SoundSource.PLAYERS, 0.8F, 1.15F);
return true;
}
public static void attach(ServerPlayer player, GrapplingHookEntity hook, BlockHitResult hitResult) {
GrappleSession session = ACTIVE.get(player.getUUID());
if (session == null || session.visualHookId != hook.getId()) {
hook.discard();
return;
}
Vec3 anchorPosition = hitResult.getLocation();
Vec3 playerCenter = playerCenter(player);
session.anchored = true;
session.anchorPosition = anchorPosition;
session.anchorBlockPos = hitResult.getBlockPos();
session.ropeLength = playerCenter.distanceTo(anchorPosition) + INITIAL_SLACK;
session.maxRopeLength = session.ropeLength + MAX_EXTRA_ROPE;
session.swingVelocity = player.getDeltaMovement();
session.held = false;
session.ropeTaut = false;
session.manualReel = false;
session.jumpHeldLastTick = false;
session.stillTicks = 0;
levelSound(player, SoundEvents.LEAD_TIED, 0.8F, 1.1F);
}
public static void expire(ServerPlayer player, GrapplingHookEntity hook) {
GrappleSession session = ACTIVE.get(player.getUUID());
if (session != null && session.visualHookId == hook.getId() && !session.anchored) {
stop(player);
playMissSound((ServerLevel) player.level(), player);
}
}
public static boolean stop(ServerPlayer player) {
return stop(player, false);
}
private static boolean stop(ServerPlayer player, boolean boostIfJumping) {
GrappleSession session = ACTIVE.remove(player.getUUID());
if (session == null) {
return false;
}
if (boostIfJumping) {
applyReleaseBoost(player, session);
}
discardVisualHook((ServerLevel) player.level(), session);
return true;
}
private static void tick(ServerPlayer player) {
GrappleSession session = ACTIVE.get(player.getUUID());
if (session == null) {
return;
}
if (!canKeepGrappling(player, session)) {
stop(player);
return;
}
Entity visualHook = ((ServerLevel) player.level()).getEntity(session.visualHookId);
if (!(visualHook instanceof GrapplingHookEntity hook)) {
ACTIVE.remove(player.getUUID());
return;
}
if (!session.anchored) {
tickFlyingHook(player, session, hook);
return;
}
Vec3 center = playerCenter(player);
double distance = center.distanceTo(session.anchorPosition);
if (distance > MAX_SESSION_DISTANCE || distance < 0.001D) {
stop(player);
return;
}
Input input = player.getLastClientInput();
updateReelState(session, input);
if (!shouldTensionRope(session, distance)) {
session.held = false;
session.ropeTaut = false;
session.swingVelocity = player.getDeltaMovement();
session.jumpHeldLastTick = session.manualReel;
return;
}
boolean firstCatch = tensionRope(session, distance) || !session.ropeTaut;
reelInIfRequested(session);
simulatePendulum(player, session, center, firstCatch);
session.ropeTaut = true;
session.jumpHeldLastTick = session.manualReel;
player.resetFallDistance();
}
private static void tickFlyingHook(ServerPlayer player, GrappleSession session, GrapplingHookEntity hook) {
session.anchorPosition = hook.position();
session.anchorBlockPos = hook.blockPosition();
session.swingVelocity = player.getDeltaMovement();
}
private static void updateReelState(GrappleSession session, Input input) {
if (session.swingVelocity.lengthSqr() < STUCK_SPEED_SQ) {
session.stillTicks++;
} else {
session.stillTicks = 0;
}
session.manualReel = input.jump();
}
private static boolean shouldTensionRope(GrappleSession session, double distance) {
return session.manualReel || session.ropeTaut || distance >= session.ropeLength - ROPE_TAUT_TOLERANCE;
}
private static boolean tensionRope(GrappleSession session, double distance) {
boolean firstHold = session.manualReel && !session.jumpHeldLastTick;
if (firstHold || (session.manualReel && !session.held)) {
double heldLength = Math.max(MIN_ROPE_LENGTH, distance + HELD_ROPE_SLACK);
session.ropeLength = Math.min(session.ropeLength, heldLength);
}
session.held = session.manualReel;
return firstHold;
}
private static void reelInIfRequested(GrappleSession session) {
if (session.manualReel) {
double reelSpeed = session.stillTicks >= STUCK_REEL_DELAY ? STUCK_REEL_IN_SPEED : REEL_IN_SPEED;
session.ropeLength = Math.max(MIN_ROPE_LENGTH, session.ropeLength - reelSpeed);
}
}
private static void simulatePendulum(ServerPlayer player, GrappleSession session, Vec3 center, boolean firstCatch) {
Vec3 radial = center.subtract(session.anchorPosition).normalize();
Vec3 currentCenter = session.anchorPosition.add(radial.scale(session.ropeLength));
Vec3 swingVelocity = firstCatch ? preservedCatchVelocity(player, radial) : projectOntoSwingPlane(session.swingVelocity, radial);
Vec3 gravity = new Vec3(0.0D, -PENDULUM_GRAVITY, 0.0D);
swingVelocity = swingVelocity.add(projectOntoSwingPlane(gravity, radial));
swingVelocity = swingVelocity.add(swingInput(player, radial)).scale(SWING_DAMPING);
swingVelocity = limitSpeed(swingVelocity);
Vec3 nextCenter = currentCenter.add(swingVelocity);
Vec3 nextRadial = nextCenter.subtract(session.anchorPosition);
if (nextRadial.lengthSqr() < 0.001D) {
nextRadial = radial;
} else {
nextRadial = nextRadial.normalize();
}
Vec3 constrainedCenter = session.anchorPosition.add(nextRadial.scale(session.ropeLength));
Vec3 actualVelocity = limitSpeed(constrainedCenter.subtract(currentCenter));
double centerOffset = player.getBbHeight() * PLAYER_CENTER_FACTOR;
session.swingVelocity = actualVelocity;
player.snapTo(constrainedCenter.x, constrainedCenter.y - centerOffset, constrainedCenter.z);
player.setDeltaMovement(actualVelocity);
player.hurtMarked = true;
}
private static Vec3 preservedCatchVelocity(ServerPlayer player, Vec3 radial) {
Vec3 incomingVelocity = player.getDeltaMovement();
Vec3 tangentVelocity = projectOntoSwingPlane(incomingVelocity, radial).add(catchLift(radial, incomingVelocity));
double fallSpeed = Math.max(0.0D, -incomingVelocity.y);
double outwardSpeed = Math.max(0.0D, incomingVelocity.dot(radial));
double targetSwingSpeed = Math.max(fallSpeed, outwardSpeed) * FALL_INERTIA_TO_SWING;
if (targetSwingSpeed <= tangentVelocity.length()) {
return tangentVelocity;
}
Vec3 swingDirection = catchSwingDirection(player, radial, tangentVelocity);
return swingDirection.scale(targetSwingSpeed);
}
private static Vec3 catchLift(Vec3 radial, Vec3 incomingVelocity) {
double speed = incomingVelocity.length();
if (speed < 0.08D) {
return Vec3.ZERO;
}
Vec3 upwardTangent = projectOntoSwingPlane(new Vec3(0.0D, 1.0D, 0.0D), radial);
if (upwardTangent.lengthSqr() < 0.001D) {
return Vec3.ZERO;
}
double fallSpeed = Math.max(0.0D, -incomingVelocity.y);
double lift = Math.min(MAX_CATCH_LIFT, speed * CATCH_LIFT_FROM_SPEED + fallSpeed * CATCH_LIFT_FROM_FALL);
return upwardTangent.normalize().scale(lift);
}
private static Vec3 swingInput(ServerPlayer player, Vec3 radial) {
Vec3 tangentIntent = projectOntoSwingPlane(player.getLastClientMoveIntent(), radial);
if (tangentIntent.lengthSqr() < 0.001D) {
return Vec3.ZERO;
}
return tangentIntent.normalize().scale(SWING_PUMP);
}
private static Vec3 catchSwingDirection(ServerPlayer player, Vec3 radial, Vec3 tangentVelocity) {
if (tangentVelocity.lengthSqr() > 0.001D) {
return tangentVelocity.normalize();
}
Vec3 moveIntent = player.getLastClientMoveIntent();
Vec3 tangentIntent = projectOntoSwingPlane(moveIntent, radial);
if (tangentIntent.lengthSqr() > 0.001D) {
return tangentIntent.normalize();
}
Vec3 look = player.calculateViewVector(player.getXRot(), player.getYRot());
Vec3 tangentLook = projectOntoSwingPlane(look, radial);
if (tangentLook.lengthSqr() > 0.001D) {
return tangentLook.normalize();
}
Vec3 horizontalFallback = new Vec3(-radial.z, 0.0D, radial.x);
if (horizontalFallback.lengthSqr() > 0.001D) {
return horizontalFallback.normalize();
}
return new Vec3(1.0D, 0.0D, 0.0D);
}
private static Vec3 projectOntoSwingPlane(Vec3 direction, Vec3 radial) {
return direction.subtract(radial.scale(direction.dot(radial)));
}
private static Vec3 limitSpeed(Vec3 velocity) {
double speed = velocity.length();
if (speed <= MAX_SWING_SPEED) {
return velocity;
}
return velocity.scale(MAX_SWING_SPEED / speed);
}
private static void applyReleaseBoost(ServerPlayer player, GrappleSession session) {
if (!player.getLastClientInput().sprint() || !canBoostOnRelease(session)) {
return;
}
Vec3 currentVelocity = player.getDeltaMovement();
Vec3 baseVelocity = currentVelocity.lengthSqr() >= session.swingVelocity.lengthSqr() ? currentVelocity : session.swingVelocity;
Vec3 look = player.calculateViewVector(player.getXRot(), player.getYRot()).normalize();
double baseSpeed = baseVelocity.length();
double boostSpeed = RELEASE_BOOST_SPEED + Math.min(MAX_RELEASE_SPEED_BOOST, baseSpeed * RELEASE_BOOST_FROM_SWING_SPEED);
Vec3 boostedVelocity = softLimitReleaseSpeed(baseVelocity.add(look.scale(boostSpeed)), baseSpeed + boostSpeed);
player.setDeltaMovement(boostedVelocity);
player.hurtMarked = true;
}
private static boolean canBoostOnRelease(GrappleSession session) {
return session.ropeTaut || session.swingVelocity.lengthSqr() >= MIN_RELEASE_SWING_SPEED_SQ;
}
private static Vec3 softLimitReleaseSpeed(Vec3 velocity, double carriedSpeed) {
double speed = velocity.length();
double maxAllowedSpeed = Math.max(MAX_RELEASE_SPEED, carriedSpeed);
if (speed <= maxAllowedSpeed) {
return velocity;
}
return velocity.scale(maxAllowedSpeed / speed);
}
private static boolean canKeepGrappling(ServerPlayer player, GrappleSession session) {
return player.isAlive()
&& player.level().dimension() == session.dimension;
}
private static Vec3 playerCenter(ServerPlayer player) {
return player.position().add(0.0D, player.getBbHeight() * PLAYER_CENTER_FACTOR, 0.0D);
}
private static Vec3 initialHookVelocity(ServerPlayer player, Vec3 look) {
Vec3 velocity = look.scale(CAST_SPEED).add(player.getDeltaMovement().scale(CAST_PLAYER_VELOCITY_FACTOR));
double speed = velocity.length();
if (speed <= MAX_INITIAL_HOOK_SPEED) {
return velocity;
}
return velocity.scale(MAX_INITIAL_HOOK_SPEED / speed);
}
private static void discardVisualHook(ServerLevel level, GrappleSession session) {
Entity entity = level.getEntity(session.visualHookId);
if (entity instanceof GrapplingHookEntity) {
entity.discard();
}
}
private static void playRetrieveSound(ServerLevel level, ServerPlayer player) {
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.LEAD_UNTIED, SoundSource.PLAYERS, 0.9F, 1.0F);
}
private static void playMissSound(ServerLevel level, ServerPlayer player) {
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.LEAD_UNTIED, SoundSource.PLAYERS, 0.35F, 0.8F);
}
private static void levelSound(ServerPlayer player, net.minecraft.sounds.SoundEvent soundEvent, float volume, float pitch) {
player.level().playSound(null, player.getX(), player.getY(), player.getZ(), soundEvent, SoundSource.PLAYERS, volume, pitch);
}
private static final class GrappleSession {
private final ResourceKey<Level> dimension;
private Vec3 anchorPosition;
private BlockPos anchorBlockPos;
private double maxRopeLength;
private final int visualHookId;
private double ropeLength;
private Vec3 swingVelocity = Vec3.ZERO;
private boolean anchored;
private boolean held;
private boolean ropeTaut;
private boolean manualReel;
private boolean jumpHeldLastTick;
private int stillTicks;
private GrappleSession(ResourceKey<Level> dimension, Vec3 anchorPosition, BlockPos anchorBlockPos, int visualHookId) {
this.dimension = dimension;
this.anchorPosition = anchorPosition;
this.anchorBlockPos = anchorBlockPos;
this.visualHookId = visualHookId;
}
}
}
@@ -0,0 +1,34 @@
package fr.koka99cab.spiderman.item;
import fr.koka99cab.spiderman.grapple.GrappleController;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.gameevent.GameEvent;
public class GrapplingRodItem extends Item {
public GrapplingRodItem(Properties properties) {
super(properties);
}
@Override
public InteractionResult use(Level level, Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
if (level instanceof ServerLevel serverLevel && player instanceof ServerPlayer serverPlayer) {
if (!GrappleController.start(serverLevel, serverPlayer, hand, stack)) {
return InteractionResult.FAIL;
}
}
player.awardStat(Stats.ITEM_USED.get(this));
stack.causeUseVibration(player, GameEvent.ITEM_INTERACT_START);
return InteractionResult.CONSUME;
}
}
@@ -0,0 +1,32 @@
package fr.koka99cab.spiderman.registry;
import fr.koka99cab.spiderman.SpidermanMod;
import fr.koka99cab.spiderman.entity.GrapplingHookEntity;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobCategory;
public final class SpidermanEntityTypes {
private static final ResourceKey<EntityType<?>> GRAPPLING_HOOK_KEY = ResourceKey.create(Registries.ENTITY_TYPE, SpidermanMod.id("grappling_hook"));
public static final EntityType<GrapplingHookEntity> GRAPPLING_HOOK = Registry.register(
BuiltInRegistries.ENTITY_TYPE,
GRAPPLING_HOOK_KEY,
EntityType.Builder.<GrapplingHookEntity>of(GrapplingHookEntity::new, MobCategory.MISC)
.sized(0.25F, 0.25F)
.clientTrackingRange(96)
.updateInterval(1)
.noSave()
.build(GRAPPLING_HOOK_KEY)
);
private SpidermanEntityTypes() {
}
public static void register() {
SpidermanMod.LOGGER.debug("Entites Spiderman enregistrees.");
}
}
@@ -0,0 +1,49 @@
package fr.koka99cab.spiderman.registry;
import fr.koka99cab.spiderman.SpidermanMod;
import fr.koka99cab.spiderman.item.GrapplingRodItem;
import net.fabricmc.fabric.api.creativetab.v1.CreativeModeTabEvents;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
public final class SpidermanItems {
private static final ResourceKey<Item> GRAPPLING_HAND_KEY = key("grappling_hand");
private static final ResourceKey<Item> GRAPPLING_ROD_KEY = key("grappling_rod");
private static final ResourceKey<CreativeModeTab> INGREDIENTS_TAB = vanillaTab("ingredients");
private static final ResourceKey<CreativeModeTab> TOOLS_TAB = vanillaTab("tools_and_utilities");
public static final Item GRAPPLING_HAND = Registry.register(
BuiltInRegistries.ITEM,
GRAPPLING_HAND_KEY,
new Item(new Item.Properties().setId(GRAPPLING_HAND_KEY))
);
public static final Item GRAPPLING_ROD = Registry.register(
BuiltInRegistries.ITEM,
GRAPPLING_ROD_KEY,
new GrapplingRodItem(new Item.Properties().durability(64).setId(GRAPPLING_ROD_KEY))
);
private SpidermanItems() {
}
public static void register() {
CreativeModeTabEvents.modifyOutputEvent(INGREDIENTS_TAB).register(entries -> entries.insertAfter(Items.TRIPWIRE_HOOK, GRAPPLING_HAND));
CreativeModeTabEvents.modifyOutputEvent(TOOLS_TAB).register(entries -> entries.insertAfter(Items.FISHING_ROD, GRAPPLING_ROD));
SpidermanMod.LOGGER.debug("Objets Spiderman enregistres.");
}
private static ResourceKey<Item> key(String path) {
return ResourceKey.create(Registries.ITEM, SpidermanMod.id(path));
}
private static ResourceKey<CreativeModeTab> vanillaTab(String path) {
return ResourceKey.create(Registries.CREATIVE_MODE_TAB, Identifier.withDefaultNamespace(path));
}
}
@@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "spiderman:item/grappling_hand"
}
}
@@ -0,0 +1,14 @@
{
"model": {
"type": "minecraft:condition",
"property": "spiderman:grappling_rod/cast",
"on_false": {
"type": "minecraft:model",
"model": "spiderman:item/grappling_rod"
},
"on_true": {
"type": "minecraft:model",
"model": "spiderman:item/grappling_rod_cast"
}
}
}
@@ -0,0 +1,4 @@
{
"item.spiderman.grappling_hand": "Grappling Hand",
"item.spiderman.grappling_rod": "Grappling Rod"
}
@@ -0,0 +1,4 @@
{
"item.spiderman.grappling_hand": "Main du grappin",
"item.spiderman.grappling_rod": "Canne-grappin"
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "spiderman:item/grappling_hook"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/handheld_rod",
"textures": {
"layer0": "spiderman:item/grappling_rod"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/handheld_rod",
"textures": {
"layer0": "spiderman:item/grappling_rod_cast"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

@@ -0,0 +1,16 @@
{
"type": "minecraft:crafting_shaped",
"category": "equipment",
"pattern": [
" I ",
"ISI",
" I "
],
"key": {
"I": "minecraft:iron_ingot",
"S": "minecraft:string"
},
"result": {
"id": "spiderman:grappling_hand"
}
}
@@ -0,0 +1,17 @@
{
"type": "minecraft:crafting_shaped",
"category": "equipment",
"pattern": [
" #",
" #X",
"# H"
],
"key": {
"#": "minecraft:stick",
"X": "minecraft:string",
"H": "spiderman:grappling_hand"
},
"result": {
"id": "spiderman:grappling_rod"
}
}
@@ -0,0 +1,32 @@
{
"schemaVersion": 1,
"id": "spiderman",
"version": "${version}",
"name": "Spiderman",
"description": "Transforme la canne a peche en grappin Fabric simple.",
"authors": [
"KOKA99CAB"
],
"license": "LGPL-3.0-or-later",
"environment": "*",
"entrypoints": {
"main": [
"fr.koka99cab.spiderman.SpidermanMod"
],
"client": [
"fr.koka99cab.spiderman.client.SpidermanClient"
]
},
"mixins": [
{
"config": "spiderman.client.mixins.json",
"environment": "client"
}
],
"depends": {
"fabricloader": ">=0.19.2",
"minecraft": "~26.1.2",
"java": ">=25",
"fabric-api": "*"
}
}
@@ -0,0 +1,11 @@
{
"required": true,
"package": "fr.koka99cab.spiderman.mixin.client",
"compatibilityLevel": "JAVA_25",
"client": [
"ConditionalItemModelPropertiesAccessor"
],
"injectors": {
"defaultRequire": 1
}
}