Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
27c9842cf1 |
9
LICENSE.txt
Normal file
9
LICENSE.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Copyright 2019 Rejah Tavi
|
||||||
|
|
||||||
|
Forged First Person is MIT Licensed:
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
26
build.gradle
26
build.gradle
@ -13,9 +13,10 @@ apply plugin: 'net.minecraftforge.gradle'
|
|||||||
apply plugin: 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
version = '1.0'
|
|
||||||
group = 'com.yourname.modid' // http://maven.apache.org/guides/mini/guide-naming-conventions.html
|
version = "${mc_version}-${mod_version}"
|
||||||
archivesBaseName = 'modid'
|
group = 'com.rejahtavi.rfp2'
|
||||||
|
archivesBaseName = 'RealFirstPerson2'
|
||||||
|
|
||||||
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
|
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
|
||||||
|
|
||||||
@ -45,6 +46,25 @@ dependencies {
|
|||||||
minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2860'
|
minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2860'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обработка ресурсов
|
||||||
|
processResources {
|
||||||
|
// Убедитесь, что задача пересоздается при изменении версий
|
||||||
|
inputs.property "version", project.version
|
||||||
|
inputs.property "mcversion", mc_version
|
||||||
|
|
||||||
|
// Замена значений в mcmod.info
|
||||||
|
from(sourceSets.main.resources.srcDirs) {
|
||||||
|
include 'mcmod.info'
|
||||||
|
expand 'version': project.version, 'mcversion': mc_version
|
||||||
|
}
|
||||||
|
|
||||||
|
// Копирование остальных файлов без изменения
|
||||||
|
from(sourceSets.main.resources.srcDirs) {
|
||||||
|
exclude 'mcmod.info'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Example for how to get properties into the manifest for reading by the runtime..
|
// Example for how to get properties into the manifest for reading by the runtime..
|
||||||
jar {
|
jar {
|
||||||
manifest {
|
manifest {
|
||||||
|
@ -3,3 +3,5 @@
|
|||||||
org.gradle.jvmargs=-Xmx3G
|
org.gradle.jvmargs=-Xmx3G
|
||||||
org.gradle.daemon=false
|
org.gradle.daemon=false
|
||||||
org.gradle.java.home=/usr/lib/jvm/openjdk8
|
org.gradle.java.home=/usr/lib/jvm/openjdk8
|
||||||
|
mc_version=1.12.2
|
||||||
|
mod_version=1.3.3
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
package com.punkcraft.example;
|
|
||||||
|
|
||||||
import net.minecraftforge.fml.common.Mod;
|
|
||||||
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
|
||||||
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
|
|
||||||
|
|
||||||
|
|
||||||
@Mod(modid = Example.MODID, name = Example.NAME, version = Example.VERSION)
|
|
||||||
public class Example {
|
|
||||||
public static final String MODID = "example";
|
|
||||||
public static final String NAME = "Example";
|
|
||||||
public static final String VERSION = "1.0";
|
|
||||||
|
|
||||||
@Mod.EventHandler
|
|
||||||
public void init(FMLInitializationEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Mod.EventHandler
|
|
||||||
public void preInit(FMLPreInitializationEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
68
src/main/java/com/rejahtavi/rfp2/ClientProxy.java
Normal file
68
src/main/java/com/rejahtavi/rfp2/ClientProxy.java
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package com.rejahtavi.rfp2;
|
||||||
|
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import net.minecraftforge.fml.client.registry.ClientRegistry;
|
||||||
|
import net.minecraftforge.fml.client.registry.RenderingRegistry;
|
||||||
|
import net.minecraftforge.fml.common.ModMetadata;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
|
||||||
|
import net.minecraftforge.fml.common.registry.EntityRegistry;
|
||||||
|
|
||||||
|
// RFP2.PROXY will be instantiated with this class if we are running as a client.
|
||||||
|
public class ClientProxy implements IProxy
|
||||||
|
{
|
||||||
|
// Called at the start of mod loading
|
||||||
|
@Override
|
||||||
|
public void preInit(FMLPreInitializationEvent event)
|
||||||
|
{
|
||||||
|
// Initialize logging
|
||||||
|
RFP2.logger = event.getModLog();
|
||||||
|
|
||||||
|
// Register mod metadata
|
||||||
|
ModMetadata m = event.getModMetadata();
|
||||||
|
m.modId = RFP2.MODID;
|
||||||
|
m.name = RFP2.MODNAME;
|
||||||
|
m.version = RFP2.MODVER;
|
||||||
|
m.description = "Implements full body rendering in first person.";
|
||||||
|
m.authorList.clear();
|
||||||
|
m.authorList.add("Rejah Tavi");
|
||||||
|
m.authorList.add("don_bruce");
|
||||||
|
m.autogenerated = false;
|
||||||
|
|
||||||
|
// Register entity rendering handler for the player dummy
|
||||||
|
RenderingRegistry.registerEntityRenderingHandler(EntityPlayerDummy.class, RenderPlayerDummy::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called after all other mod preInit()s have run
|
||||||
|
@Override
|
||||||
|
public void init(FMLInitializationEvent event)
|
||||||
|
{
|
||||||
|
// Load config
|
||||||
|
RFP2.config = new RFP2Config();
|
||||||
|
|
||||||
|
// Register keybinds
|
||||||
|
ClientRegistry.registerKeyBinding(RFP2.keybindArmsToggle.keyBindingInstance);
|
||||||
|
ClientRegistry.registerKeyBinding(RFP2.keybindModToggle.keyBindingInstance);
|
||||||
|
ClientRegistry.registerKeyBinding(RFP2.keybindHeadRotationToggle.keyBindingInstance);
|
||||||
|
|
||||||
|
// Register player dummy entity
|
||||||
|
EntityRegistry.registerModEntity(new ResourceLocation(RFP2.MODID, "PlayerDummy"), EntityPlayerDummy.class, "PlayerDummy", 0, RFP2.MODID, 5, 100, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called after all other mod init()s have run
|
||||||
|
@Override
|
||||||
|
public void postInit(FMLPostInitializationEvent event)
|
||||||
|
{
|
||||||
|
// Begin tracking state
|
||||||
|
RFP2.state = new RFP2State();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when starting up a dedicated server
|
||||||
|
@Override
|
||||||
|
public void serverStarting(FMLServerStartingEvent event)
|
||||||
|
{
|
||||||
|
// This will never get called on client side
|
||||||
|
}
|
||||||
|
}
|
123
src/main/java/com/rejahtavi/rfp2/EntityPlayerDummy.java
Normal file
123
src/main/java/com/rejahtavi/rfp2/EntityPlayerDummy.java
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package com.rejahtavi.rfp2;
|
||||||
|
|
||||||
|
import net.minecraft.block.BlockLiquid;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.entity.player.EntityPlayer;
|
||||||
|
import net.minecraft.nbt.NBTTagCompound;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class implements the functionality of the PlayerDummy entity.
|
||||||
|
*
|
||||||
|
* The PlayerDummy's presence is used as a trigger to draw the fake player body every frame,
|
||||||
|
* as well as a way to manipulate the precise positioning of the fake player body.
|
||||||
|
*/
|
||||||
|
public class EntityPlayerDummy extends Entity
|
||||||
|
{
|
||||||
|
// Stores last time the entity state changed
|
||||||
|
public long lastTickUpdated;
|
||||||
|
|
||||||
|
// Stores the last known swimming state
|
||||||
|
private boolean swimming = false;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
public EntityPlayerDummy(World world)
|
||||||
|
{
|
||||||
|
// Call parent Entity() constructor with appropriate world reference
|
||||||
|
super(world);
|
||||||
|
|
||||||
|
// Set up new dummy object
|
||||||
|
this.ignoreFrustumCheck = true;
|
||||||
|
this.setSize(0, 2);
|
||||||
|
this.lastTickUpdated = world.getTotalWorldTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when entity should update itself
|
||||||
|
public void onUpdate()
|
||||||
|
{
|
||||||
|
// Get reference to current local player and null check it
|
||||||
|
EntityPlayer player = Minecraft.getMinecraft().player;
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
// Can't find our player, so remove ourself from the world
|
||||||
|
this.setDead();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Record the current tick number to prove we did an update
|
||||||
|
this.lastTickUpdated = world.getTotalWorldTime();
|
||||||
|
|
||||||
|
// Match our position and rotation to player, then record the current tick
|
||||||
|
this.setPositionAndRotation(player.posX, player.posY, player.posZ, player.rotationYaw, player.rotationPitch);
|
||||||
|
|
||||||
|
// Update swimming status; some conditions are necessary to start or stop swimming
|
||||||
|
// this provides hysteresis and avoids any "flickering" of the swimming state
|
||||||
|
|
||||||
|
// Get references to the block at the player's feet, plus one above and one below
|
||||||
|
BlockPos atHead = new BlockPos(player.posX, player.posY + player.eyeHeight, player.posZ);
|
||||||
|
BlockPos atFeet = new BlockPos(player.posX, player.posY, player.posZ);
|
||||||
|
BlockPos belowFeet = atFeet.down();
|
||||||
|
|
||||||
|
// Check each block location for liquid
|
||||||
|
boolean liquidAtHead = player.world.getBlockState(atHead).getBlock() instanceof BlockLiquid;
|
||||||
|
boolean liquidAtFeet = player.world.getBlockState(atFeet).getBlock() instanceof BlockLiquid;
|
||||||
|
boolean liquidBelowFeet = player.world.getBlockState(belowFeet).getBlock() instanceof BlockLiquid;
|
||||||
|
|
||||||
|
// Are we currently swimming?
|
||||||
|
if (swimming)
|
||||||
|
{
|
||||||
|
// Currently swimming. Figure out if we should stop.
|
||||||
|
if (RFP2Config.compatibility.useAggressiveSwimmingCheck)
|
||||||
|
{
|
||||||
|
// use aggressive version of swimming checks
|
||||||
|
// requires player to FULLY clear water to stop swimming
|
||||||
|
if (!liquidAtHead && !liquidAtFeet && !liquidBelowFeet) swimming = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use normal version of swimming checks
|
||||||
|
// considers player no longer swimming when standing in 1 block deep water
|
||||||
|
if (!liquidAtHead && !liquidBelowFeet) swimming = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Currently NOT swimming. Figure out if we should start.
|
||||||
|
if (RFP2Config.compatibility.useAggressiveSwimmingCheck)
|
||||||
|
{
|
||||||
|
// use aggressive version of swimming checks
|
||||||
|
// only requires player to touch water to start swimming
|
||||||
|
// (below feet not checked, because you can "lean over" the edge of water and this is definitely not swimming)
|
||||||
|
if (liquidAtHead || liquidAtFeet) swimming = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use normal version of swimming checks
|
||||||
|
// only considers player swimming once their head goes under
|
||||||
|
if (liquidAtHead && liquidAtFeet) swimming = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns whether player is swimming or not
|
||||||
|
public boolean isSwimming()
|
||||||
|
{
|
||||||
|
return swimming;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remaining methods are required by the <Entity> interface but we don't have anything special to do in them.
|
||||||
|
public void entityInit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readEntityFromNBT(NBTTagCompound x)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeEntityToNBT(NBTTagCompound x)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
18
src/main/java/com/rejahtavi/rfp2/IProxy.java
Normal file
18
src/main/java/com/rejahtavi/rfp2/IProxy.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package com.rejahtavi.rfp2;
|
||||||
|
|
||||||
|
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
|
||||||
|
|
||||||
|
public interface IProxy
|
||||||
|
{
|
||||||
|
// FML life cycle events for mod loading
|
||||||
|
void preInit(FMLPreInitializationEvent event);
|
||||||
|
|
||||||
|
void init(FMLInitializationEvent event);
|
||||||
|
|
||||||
|
void postInit(FMLPostInitializationEvent event);
|
||||||
|
|
||||||
|
void serverStarting(FMLServerStartingEvent event);
|
||||||
|
}
|
193
src/main/java/com/rejahtavi/rfp2/RFP2.java
Normal file
193
src/main/java/com/rejahtavi/rfp2/RFP2.java
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package com.rejahtavi.rfp2;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import org.apache.logging.log4j.Level;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.lwjgl.input.Keyboard;
|
||||||
|
import com.rejahtavi.rfp2.compat.RFP2CompatApi;
|
||||||
|
import com.rejahtavi.rfp2.compat.handlers.RFP2CompatHandler;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.entity.player.EntityPlayer;
|
||||||
|
import net.minecraft.util.text.ITextComponent;
|
||||||
|
import net.minecraft.util.text.TextComponentString;
|
||||||
|
import net.minecraft.util.text.TextFormatting;
|
||||||
|
import net.minecraftforge.fml.common.Loader;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.common.Mod.EventHandler;
|
||||||
|
import net.minecraftforge.fml.common.SidedProxy;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||||
|
|
||||||
|
// Register mod with forge
|
||||||
|
@Mod(modid = RFP2.MODID, name = RFP2.MODNAME, version = RFP2.MODVER, dependencies = RFP2.MODDEPS, clientSideOnly = true, acceptedMinecraftVersions = "1.12.2", acceptableRemoteVersions = "*")
|
||||||
|
public class RFP2 {
|
||||||
|
// Conflicting Mods
|
||||||
|
public static final String[] CONFLICT_MODIDS = { "obfuscate", "moreplayermodels", "playerformlittlemaid" };
|
||||||
|
|
||||||
|
// Mod info
|
||||||
|
public static final String MODID = "rfp2";
|
||||||
|
public static final String MODNAME = "Real First Person 2";
|
||||||
|
public static final String MODVER = "@VERSION@";
|
||||||
|
|
||||||
|
// Provide list of mods to load after, so that that compatibility handlers can
|
||||||
|
// load correctly.
|
||||||
|
public static final String MODDEPS = (
|
||||||
|
("after:obfuscate;")
|
||||||
|
+ ("after:moreplayermodels;"));
|
||||||
|
|
||||||
|
// Collection of compatibility handler objects
|
||||||
|
public static ArrayList<RFP2CompatHandler> compatHandlers = new ArrayList<RFP2CompatHandler>();
|
||||||
|
|
||||||
|
// Constants controlling dummy behavior
|
||||||
|
public static final int DUMMY_MIN_RESPAWN_INTERVAL = 40; // min ticks between spawn attempts
|
||||||
|
public static final int DUMMY_UPDATE_TIMEOUT = 20; // max ticks between dummy entity updates
|
||||||
|
public static final int DUMMY_MAX_SEPARATION = 5; // max blocks separation between dummy and player
|
||||||
|
|
||||||
|
// Constants controlling compatibility
|
||||||
|
public static final int MAX_SUSPEND_TIMER = 60; // maximum number of ticks another mod may suspend RFP2 for
|
||||||
|
public static final int MIN_IGNORED_ERROR_LOG_INTERVAL = 60; // interval between logging events when errors are
|
||||||
|
// ignored.
|
||||||
|
|
||||||
|
// Constants controlling optimization / load limiting
|
||||||
|
// every 4 ticks is enough for global mod enable/disable checks
|
||||||
|
public static final int MIN_ACTIVATION_CHECK_INTERVAL = 4; // min ticks between mod enable checks
|
||||||
|
public static final long MIN_TICKS_BETWEEN_ERROR_LOGS = 1200; // only log errors once per minute (20tps * 60s/m)
|
||||||
|
|
||||||
|
// arm checks need to be faster to keep up with hotbar scrolling, but we still
|
||||||
|
// want to limit it to once per tick.
|
||||||
|
public static final int MIN_REAL_ARMS_CHECK_INTERVAL = 1; // min ticks between arms enable checks
|
||||||
|
|
||||||
|
// Main class instance forge will use to reference the mod
|
||||||
|
@Mod.Instance(MODID)
|
||||||
|
public static RFP2 INSTANCE;
|
||||||
|
|
||||||
|
// The proxy reference will be set to either ClientProxy or ServerProxy
|
||||||
|
// depending on execution context.
|
||||||
|
@SidedProxy(clientSide = "com.rejahtavi." + MODID + ".ClientProxy", serverSide = "com.rejahtavi." + MODID
|
||||||
|
+ ".ServerProxy")
|
||||||
|
public static IProxy PROXY;
|
||||||
|
|
||||||
|
// Key bindings
|
||||||
|
public static RFP2Keybind keybindArmsToggle = new RFP2Keybind("key.arms.desc", Keyboard.KEY_SEMICOLON,
|
||||||
|
"key.rfp2.category");
|
||||||
|
public static RFP2Keybind keybindModToggle = new RFP2Keybind("key.mod.desc", Keyboard.KEY_APOSTROPHE,
|
||||||
|
"key.rfp2.category");
|
||||||
|
public static RFP2Keybind keybindHeadRotationToggle = new RFP2Keybind("key.head.desc", Keyboard.KEY_H,
|
||||||
|
"key.rfp2.category");
|
||||||
|
|
||||||
|
// State objects
|
||||||
|
public static RFP2Config config;
|
||||||
|
public static RFP2State state;
|
||||||
|
public static Logger logger;
|
||||||
|
public static long lastLoggedTimestamp = 0;
|
||||||
|
public static long ignoredErrorCount = 0;
|
||||||
|
|
||||||
|
// Handles for optionally integrating with other mods
|
||||||
|
public static RFP2CompatApi api = new RFP2CompatApi();
|
||||||
|
// public static RFP2CompatHandlerCosarmor compatCosArmor = null;
|
||||||
|
// public static RFP2CompatHandlerMorph compatMorph = null;
|
||||||
|
|
||||||
|
// Sets the logging level for most messages written by the mod -- higher levels
|
||||||
|
// are usually highlighted in launchers.
|
||||||
|
public static final Level LOGGING_LEVEL_DEBUG = Level.DEBUG;
|
||||||
|
public static final Level LOGGING_LEVEL_LOW = Level.INFO;
|
||||||
|
public static final Level LOGGING_LEVEL_MED = Level.WARN;
|
||||||
|
public static final Level LOGGING_LEVEL_HIGH = Level.FATAL;
|
||||||
|
|
||||||
|
// Mod Initialization - call correct proxy events based on the @SidedProxy
|
||||||
|
// picked above
|
||||||
|
@EventHandler
|
||||||
|
public void preInit(FMLPreInitializationEvent event) {
|
||||||
|
PROXY.preInit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void init(FMLInitializationEvent event) {
|
||||||
|
PROXY.init(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void postInit(FMLPostInitializationEvent event) {
|
||||||
|
String logMessage = "";
|
||||||
|
|
||||||
|
// Initialize compatibility handlers
|
||||||
|
compatHandlers = new ArrayList<RFP2CompatHandler>();
|
||||||
|
|
||||||
|
// If any compatibility handlers were loaded, log them.
|
||||||
|
if (logMessage.length() > 0) {
|
||||||
|
logMessage = "Compatibility handler(s) loaded for: " + (logMessage.substring(0, logMessage.length() - 2))
|
||||||
|
+ ".";
|
||||||
|
RFP2.logger.log(LOGGING_LEVEL_MED, logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inform Forge we're done with our postInit() phase
|
||||||
|
PROXY.postInit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provides facility to write a message to the local player's chat log
|
||||||
|
public static void logToChat(String message) {
|
||||||
|
// get a reference to the player
|
||||||
|
EntityPlayer player = Minecraft.getMinecraft().player;
|
||||||
|
if (player != null) {
|
||||||
|
// compose text component from message string and send it to the player
|
||||||
|
ITextComponent textToSend = new TextComponentString(message);
|
||||||
|
player.sendMessage(textToSend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provides facility to write a message to the local player's chat log
|
||||||
|
public static void logToChatByPlayer(String message, EntityPlayer player) {
|
||||||
|
// get a reference to the player
|
||||||
|
if (player != null) {
|
||||||
|
// compose text component from message string and send it to the player
|
||||||
|
ITextComponent textToSend = new TextComponentString(message);
|
||||||
|
player.sendMessage(textToSend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void errorDisableMod(String sourceMethod, Exception e) {
|
||||||
|
// If anything goes wrong, this method will be called to shut off the mod and
|
||||||
|
// write an error to the logs.
|
||||||
|
// The user can still try to re-enable it with a keybind or via the config gui.
|
||||||
|
// This might just result in another error, but at least it will prevent us from
|
||||||
|
// slowing down the game or flooding the logs if something is really broken.
|
||||||
|
|
||||||
|
if (RFP2Config.compatibility.disableRenderErrorCatching) {
|
||||||
|
// Get current epoch
|
||||||
|
long epoch = System.currentTimeMillis() / 1000L;
|
||||||
|
|
||||||
|
// Check if it has been long enough since our last logging event
|
||||||
|
if (epoch >= (lastLoggedTimestamp + MIN_IGNORED_ERROR_LOG_INTERVAL)) {
|
||||||
|
// Write error to log but continue
|
||||||
|
RFP2.logger.log(LOGGING_LEVEL_MED, ": " + sourceMethod + " **IGNORING** exception:" + e.getMessage());
|
||||||
|
// Announce number of errors ignored since last report
|
||||||
|
if (ignoredErrorCount > 0) {
|
||||||
|
RFP2.logger.log(LOGGING_LEVEL_MED, ": (" + ignoredErrorCount + " errors ignored in last "
|
||||||
|
+ MIN_IGNORED_ERROR_LOG_INTERVAL + "s.)");
|
||||||
|
}
|
||||||
|
// reset counter and timer
|
||||||
|
ignoredErrorCount = 0;
|
||||||
|
lastLoggedTimestamp = epoch;
|
||||||
|
} else {
|
||||||
|
// hasn't been long enough, just increment the counter
|
||||||
|
ignoredErrorCount += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Temporarily disable the mod
|
||||||
|
RFP2.state.enableMod = false;
|
||||||
|
|
||||||
|
// Write an error, including a stack trace, to the logs
|
||||||
|
RFP2.logger.log(LOGGING_LEVEL_HIGH, ": first person rendering deactivated.");
|
||||||
|
RFP2.logger.log(LOGGING_LEVEL_HIGH, ": " + sourceMethod + " encountered an exception:" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// Announce the issue to the player in-game
|
||||||
|
RFP2.logToChat(RFP2.MODNAME + " mod " + TextFormatting.RED + " disabled");
|
||||||
|
RFP2.logToChat(sourceMethod + " encountered an exception:");
|
||||||
|
RFP2.logToChat(TextFormatting.RED + e.getMessage());
|
||||||
|
RFP2.logToChat(TextFormatting.DARK_RED + e.getStackTrace().toString());
|
||||||
|
RFP2.logToChat(TextFormatting.GOLD + "Please check your minecraft log file for more details.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
160
src/main/java/com/rejahtavi/rfp2/RFP2Config.java
Normal file
160
src/main/java/com/rejahtavi/rfp2/RFP2Config.java
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package com.rejahtavi.rfp2;
|
||||||
|
|
||||||
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
|
import net.minecraftforge.common.config.Config;
|
||||||
|
import net.minecraftforge.common.config.Config.Comment;
|
||||||
|
import net.minecraftforge.common.config.Config.Name;
|
||||||
|
import net.minecraftforge.common.config.Config.RangeDouble;
|
||||||
|
import net.minecraftforge.common.config.Config.Type;
|
||||||
|
import net.minecraftforge.common.config.ConfigManager;
|
||||||
|
import net.minecraftforge.fml.client.event.ConfigChangedEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.relauncher.Side;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Config Annotation system documentation can be found here:
|
||||||
|
* https://mcforge.readthedocs.io/en/latest/config/annotations/
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Identify this as the config class to forge
|
||||||
|
@Config(
|
||||||
|
modid = RFP2.MODID,
|
||||||
|
type = Type.INSTANCE,
|
||||||
|
name = RFP2.MODID,
|
||||||
|
category = "")
|
||||||
|
@Mod.EventBusSubscriber(Side.CLIENT)
|
||||||
|
public class RFP2Config
|
||||||
|
{
|
||||||
|
// Constructor
|
||||||
|
public RFP2Config()
|
||||||
|
{
|
||||||
|
// Register the config handler to the bus so that it shows up in the forge config gui
|
||||||
|
MinecraftForge.EVENT_BUS.register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create preferences section
|
||||||
|
@Comment({ "Personal preferences for " + RFP2.MODNAME })
|
||||||
|
@Name("Preferences")
|
||||||
|
public static final Preferences preferences = new Preferences();
|
||||||
|
|
||||||
|
// Create compatibility section
|
||||||
|
@Comment("Item and Mount compatability lists for " + RFP2.MODNAME)
|
||||||
|
@Name("Compatability")
|
||||||
|
public static final Compatibility compatibility = new Compatibility();
|
||||||
|
|
||||||
|
// Define structure and defaults of Preferences section
|
||||||
|
public static class Preferences
|
||||||
|
{
|
||||||
|
@Comment({ "Enables/disables mod at startup.", "Default: true" })
|
||||||
|
@Name("Enable Mod")
|
||||||
|
public boolean enableMod = true;
|
||||||
|
|
||||||
|
@Comment({ "Enables/disables real arms at startup", "Default: true" })
|
||||||
|
@Name("Enable Real Arm Rendering")
|
||||||
|
public boolean enableRealArms = true;
|
||||||
|
|
||||||
|
@Comment({ "Enables/disables head turning at startup", "Default: false" })
|
||||||
|
@Name("Enable Head Turning")
|
||||||
|
public boolean enableHeadTurning = false;
|
||||||
|
|
||||||
|
@Comment({ "Enables/disables status messages when a keybind is pressed.", "Default: false" })
|
||||||
|
@Name("Enable Status Messages")
|
||||||
|
public boolean enableStatusMessages = true;
|
||||||
|
|
||||||
|
@Comment({ "How far behind the camera to put the first person player model", "Default: 0.35" })
|
||||||
|
@Name("Player Model Offset")
|
||||||
|
@RangeDouble(
|
||||||
|
min = 0.0f,
|
||||||
|
max = 2.0f)
|
||||||
|
public double playerModelOffset = 0.35f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define structure and defaults of Compatibility section
|
||||||
|
public static class Compatibility
|
||||||
|
{
|
||||||
|
@Comment({ "Vanilla arms are used when holding one of these items.",
|
||||||
|
"Needed for compasses and maps, stops big items blocking the view.",
|
||||||
|
"Note: Not case sensitive, accepts simple item names and regex patterns:",
|
||||||
|
".* = wildcard, ^ = match beginning of name, $ = match end of name." })
|
||||||
|
@Name("Held Item Conflicts")
|
||||||
|
public String[] heldItemConflictList = { "minecraft:filled_map",
|
||||||
|
"minecraft:clock",
|
||||||
|
"minecraft:shield",
|
||||||
|
"minecraft:bow",
|
||||||
|
"slashblade:.*",
|
||||||
|
".*compass$",
|
||||||
|
"tconstruct:.*bow",
|
||||||
|
"tconstruct:battlesign",
|
||||||
|
"thermalfoundation:shield_.*" };
|
||||||
|
|
||||||
|
@Comment({ "Mod temporarily disables when riding one of these mounts.",
|
||||||
|
"Stops legs clipping through minecarts.",
|
||||||
|
"Note: Not case sensitive, accepts simple item names and regex patterns.",
|
||||||
|
".* = wildcard, ^ = match beginning of name, $ = match end of name." })
|
||||||
|
@Name("Mount Conflicts")
|
||||||
|
public String[] mountConflictList = { ".*minecart.*" };
|
||||||
|
|
||||||
|
@Comment("Disables the mod when swimming.")
|
||||||
|
@Name("Disable when swimming")
|
||||||
|
public boolean disableWhenSwimming = false;
|
||||||
|
|
||||||
|
@Comment("Enforces a more aggressive version of the swimming checks.")
|
||||||
|
@Name("Use aggressive swimming checks")
|
||||||
|
public boolean useAggressiveSwimmingCheck = false;
|
||||||
|
|
||||||
|
@Comment("Disables the mod when sneaking.")
|
||||||
|
@Name("Disable when sneaking")
|
||||||
|
public boolean disableWhenSneaking = false;
|
||||||
|
|
||||||
|
@Comment("Switches to vanilla arms when *any* item is held, not just conflict items.")
|
||||||
|
@Name("Use vanilla arms when holding any item")
|
||||||
|
public boolean disableArmsWhenAnyItemHeld = false;
|
||||||
|
|
||||||
|
@Comment("Disables rendering safety checks. May enable compatibility with mods that cause rendering exceptions, but cannot guarantee that the game will be stable.")
|
||||||
|
@Name("Ignore rendering errors (not recommended).")
|
||||||
|
public boolean disableRenderErrorCatching = false;
|
||||||
|
|
||||||
|
@Comment("Suppresses alerts about incompatible mods in chat on startup.")
|
||||||
|
@Name("Suppress startup compatibility alert (not recommended).")
|
||||||
|
public boolean disableModCompatibilityAlerts = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to configuration change event
|
||||||
|
// This fires whenever the user saves changes in the config gui.
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onConfigChanged(final ConfigChangedEvent.OnConfigChangedEvent event)
|
||||||
|
{
|
||||||
|
// Only respond to events meant for us
|
||||||
|
if (event.getModID().contentEquals(RFP2.MODID))
|
||||||
|
{
|
||||||
|
// Inject the new values and save to the config file when the config has been changed from the GUI.
|
||||||
|
RFP2.logger.log(RFP2.LOGGING_LEVEL_LOW, "synchronizing config file.");
|
||||||
|
|
||||||
|
// Make sure all referenced items are lower case (makes matching later computationally cheaper)
|
||||||
|
RFP2Config.compatibility.heldItemConflictList = lowerCaseArray(RFP2Config.compatibility.heldItemConflictList);
|
||||||
|
RFP2Config.compatibility.mountConflictList = lowerCaseArray(RFP2Config.compatibility.mountConflictList);
|
||||||
|
|
||||||
|
// Save the config
|
||||||
|
ConfigManager.sync(RFP2.MODID, Config.Type.INSTANCE);
|
||||||
|
|
||||||
|
// Update current state to match preferences that were just selected in the GUI
|
||||||
|
RFP2.state.enableMod = preferences.enableMod;
|
||||||
|
RFP2.state.enableRealArms = preferences.enableRealArms;
|
||||||
|
RFP2.state.enableHeadTurning = preferences.enableHeadTurning;
|
||||||
|
RFP2.state.enableStatusMessages = preferences.enableStatusMessages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes in an array of strings and converts all members of it to lower case
|
||||||
|
private static String[] lowerCaseArray(String[] array)
|
||||||
|
{
|
||||||
|
// Iterate over all elements in the array
|
||||||
|
for (int i = 0; i < array.length; i++)
|
||||||
|
{
|
||||||
|
// Rewrite each element in the array into lower case
|
||||||
|
array[i] = array[i].toLowerCase();
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
}
|
54
src/main/java/com/rejahtavi/rfp2/RFP2Keybind.java
Normal file
54
src/main/java/com/rejahtavi/rfp2/RFP2Keybind.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package com.rejahtavi.rfp2;
|
||||||
|
|
||||||
|
import net.minecraft.client.settings.KeyBinding;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This helper class implements basic key bindings and state tracking of each key
|
||||||
|
* Notes:
|
||||||
|
* Each key binding will spawn its own instance of this class
|
||||||
|
* Each instance will track the last known state of the binding, and watch for "rising edges" to trigger on
|
||||||
|
* This prevents annoying "multi-triggers" if the button is held down for more than one frame
|
||||||
|
*/
|
||||||
|
public class RFP2Keybind
|
||||||
|
{
|
||||||
|
|
||||||
|
// Handle to the current instance
|
||||||
|
public KeyBinding keyBindingInstance;
|
||||||
|
|
||||||
|
// Tracks state of the key on the previous tick
|
||||||
|
private boolean wasPressed = false;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
public RFP2Keybind(String description, int keyCode, String category)
|
||||||
|
{
|
||||||
|
keyBindingInstance = new KeyBinding(description, keyCode, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function acts as a monostable filter to isolate "rising edges" of key press signals
|
||||||
|
*
|
||||||
|
* In other words, this returns true ONLY on the very first tick key is pressed, and false at all other times.
|
||||||
|
* The player must release and re-press the key to get a second event to fire.
|
||||||
|
*
|
||||||
|
* This stops the mod from spam-toggling options when a key is held.
|
||||||
|
*/
|
||||||
|
public boolean checkForNewPress()
|
||||||
|
{
|
||||||
|
|
||||||
|
// Check current key state
|
||||||
|
boolean currentlyPressed = this.keyBindingInstance.isKeyDown();
|
||||||
|
if (!wasPressed && currentlyPressed)
|
||||||
|
{
|
||||||
|
// The key WASN'T pressed before, but now it is; Winner! Return True!
|
||||||
|
wasPressed = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Something else happened and we don't particularly care what.
|
||||||
|
// Save the current state of the button and return false.
|
||||||
|
wasPressed = currentlyPressed;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
515
src/main/java/com/rejahtavi/rfp2/RFP2State.java
Normal file
515
src/main/java/com/rejahtavi/rfp2/RFP2State.java
Normal file
@ -0,0 +1,515 @@
|
|||||||
|
package com.rejahtavi.rfp2;
|
||||||
|
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.entity.player.EntityPlayer;
|
||||||
|
import net.minecraft.util.text.TextFormatting;
|
||||||
|
import net.minecraftforge.client.event.RenderHandEvent;
|
||||||
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
|
import net.minecraftforge.fml.common.Loader;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.common.eventhandler.EventPriority;
|
||||||
|
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.gameevent.InputEvent.KeyInputEvent;
|
||||||
|
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||||
|
import net.minecraftforge.fml.relauncher.Side;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class receives and processes all events related to the player dummy.
|
||||||
|
* It is also responsible for processing events related to keybinds and configuration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// register this class as an event handler with forge
|
||||||
|
@Mod.EventBusSubscriber(Side.CLIENT)
|
||||||
|
public class RFP2State
|
||||||
|
{
|
||||||
|
// Local objects to track mod internal state
|
||||||
|
|
||||||
|
// handle to the player dummy entity
|
||||||
|
EntityPlayerDummy dummy;
|
||||||
|
|
||||||
|
// timers for performance waits
|
||||||
|
int spawnDelay;
|
||||||
|
long checkEnableModDelay;
|
||||||
|
long checkEnableRealArmsDelay;
|
||||||
|
int suspendApiDelay;
|
||||||
|
|
||||||
|
// state flags
|
||||||
|
boolean lastActivateCheckResult;
|
||||||
|
boolean lastRealArmsCheckResult;
|
||||||
|
boolean enableMod;
|
||||||
|
boolean enableRealArms;
|
||||||
|
boolean enableHeadTurning;
|
||||||
|
boolean enableStatusMessages;
|
||||||
|
boolean conflictsDetected = false;
|
||||||
|
boolean conflictCheckDone = false;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
public RFP2State()
|
||||||
|
{
|
||||||
|
// No dummy exists at startup
|
||||||
|
dummy = null;
|
||||||
|
|
||||||
|
// Start a timer so that we wait a bit for things to load before first trying to spawn the dummy
|
||||||
|
spawnDelay = RFP2.DUMMY_MIN_RESPAWN_INTERVAL;
|
||||||
|
|
||||||
|
// Initialize local variables
|
||||||
|
checkEnableModDelay = 0;
|
||||||
|
checkEnableRealArmsDelay = 0;
|
||||||
|
suspendApiDelay = 0;
|
||||||
|
lastActivateCheckResult = true;
|
||||||
|
lastRealArmsCheckResult = true;
|
||||||
|
|
||||||
|
// Import initial state from config file
|
||||||
|
enableMod = RFP2Config.preferences.enableMod;
|
||||||
|
enableRealArms = RFP2Config.preferences.enableRealArms;
|
||||||
|
enableHeadTurning = RFP2Config.preferences.enableHeadTurning;
|
||||||
|
enableStatusMessages = RFP2Config.preferences.enableStatusMessages;
|
||||||
|
|
||||||
|
// Register ourselves on the bus so we can receive and process events
|
||||||
|
MinecraftForge.EVENT_BUS.register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive key press events for key binding handling
|
||||||
|
@SubscribeEvent(
|
||||||
|
priority = EventPriority.NORMAL,
|
||||||
|
receiveCanceled = true)
|
||||||
|
public void onEvent(KeyInputEvent event)
|
||||||
|
{
|
||||||
|
// DISABLED for 1.3.1 -- now only warns players
|
||||||
|
// kill mod completely when a conflict is detected.
|
||||||
|
// if (this.conflictsDetected) return;
|
||||||
|
|
||||||
|
// Check key binding in turn for new presses
|
||||||
|
if (RFP2.keybindArmsToggle.checkForNewPress())
|
||||||
|
{
|
||||||
|
enableRealArms = !enableRealArms;
|
||||||
|
if (enableStatusMessages)
|
||||||
|
{
|
||||||
|
// log keybind-triggered state changes to chat if configured to do so
|
||||||
|
RFP2.logToChat(RFP2.MODNAME + " arms " + (enableRealArms ? TextFormatting.GREEN + "enabled" : TextFormatting.RED + "disabled"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check key binding in turn for new presses
|
||||||
|
if (RFP2.keybindModToggle.checkForNewPress())
|
||||||
|
{
|
||||||
|
enableMod = !enableMod;
|
||||||
|
if (enableStatusMessages)
|
||||||
|
{
|
||||||
|
// log keybind-triggered state changes to chat if configured to do so
|
||||||
|
RFP2.logToChat(RFP2.MODNAME + " mod " + (enableMod ? TextFormatting.GREEN + "enabled" : TextFormatting.RED + "disabled"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check key binding in turn for new presses
|
||||||
|
if (RFP2.keybindHeadRotationToggle.checkForNewPress())
|
||||||
|
{
|
||||||
|
enableHeadTurning = !enableHeadTurning;
|
||||||
|
if (enableStatusMessages)
|
||||||
|
{
|
||||||
|
// log keybind-triggered state changes to chat if configured to do so
|
||||||
|
RFP2.logToChat(RFP2.MODNAME + " head rotation " + (enableHeadTurning ? TextFormatting.GREEN + "enabled" : TextFormatting.RED + "disabled"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true when mod conflicts are detected
|
||||||
|
public void detectModConflicts(EntityPlayer player)
|
||||||
|
{
|
||||||
|
// Only let this routine run once per startup
|
||||||
|
if (!this.conflictCheckDone)
|
||||||
|
{
|
||||||
|
// Check for conflicting mods
|
||||||
|
String modConflictList = "";
|
||||||
|
for (String conflictingID : RFP2.CONFLICT_MODIDS)
|
||||||
|
{
|
||||||
|
if (Loader.isModLoaded(conflictingID))
|
||||||
|
{
|
||||||
|
if (modConflictList.length() != 0) modConflictList += ", ";
|
||||||
|
modConflictList += conflictingID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if we got any hits
|
||||||
|
if (modConflictList.length() != 0)
|
||||||
|
{
|
||||||
|
// If mod compatibility alerts are disabled, JUST put info in the log file.
|
||||||
|
if (RFP2Config.compatibility.disableModCompatibilityAlerts)
|
||||||
|
{
|
||||||
|
RFP2.logger.log(RFP2.LOGGING_LEVEL_HIGH, this.getClass().getName() + ": WARNING: In-game compatibility alerts have been disabled!");
|
||||||
|
// this.enableMod unchanged
|
||||||
|
// this.disabledForConflict unchanged
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Mod compatibility alerts are enabled -- warn the player via in-game chat that something is amiss
|
||||||
|
//@formatter:off
|
||||||
|
RFP2.logToChatByPlayer("" + TextFormatting.BOLD + TextFormatting.GOLD
|
||||||
|
+ "WARNING: RFP2 has known compatibility issues with the mod(s): "
|
||||||
|
+ TextFormatting.RESET + TextFormatting.RED
|
||||||
|
+ modConflictList + ".", player);
|
||||||
|
|
||||||
|
RFP2.logToChatByPlayer("" + TextFormatting.BOLD + TextFormatting.GOLD
|
||||||
|
+ "Be aware that visual glitches may occur.", player);
|
||||||
|
|
||||||
|
RFP2.logToChatByPlayer("Press the hotkey (Default: Apostrophe) to use RFP2 anyway.", player);
|
||||||
|
|
||||||
|
RFP2.logToChatByPlayer("" + TextFormatting.RESET + TextFormatting.GRAY
|
||||||
|
+ "(You can disable this warning in mod options.)", player);
|
||||||
|
//@formatter:on
|
||||||
|
this.conflictsDetected = true;
|
||||||
|
this.enableMod = false;
|
||||||
|
}
|
||||||
|
RFP2.logger.log(RFP2.LOGGING_LEVEL_HIGH, this.getClass().getName() + ": WARNING: Detected conflicting mod(s): " + modConflictList);
|
||||||
|
}
|
||||||
|
this.conflictCheckDone = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive event when player hands are about to be drawn
|
||||||
|
@SubscribeEvent(
|
||||||
|
priority = EventPriority.HIGHEST)
|
||||||
|
public void onEvent(RenderHandEvent event)
|
||||||
|
{
|
||||||
|
// DISABLED for 1.3.1 -- now only warns players
|
||||||
|
// kill mod completely when a conflict is detected.
|
||||||
|
// if (this.conflictsDetected) return;
|
||||||
|
|
||||||
|
// Get local player reference
|
||||||
|
EntityPlayer player = Minecraft.getMinecraft().player;
|
||||||
|
// if: 1) player exists AND 2) mod is active AND 3) rendering real arms is active
|
||||||
|
if (player != null && RFP2.state.isModEnabled(player) && RFP2.state.isRealArmsEnabled(player))
|
||||||
|
{
|
||||||
|
// then skip drawing the vanilla 2D HUD arms by canceling the event
|
||||||
|
event.setCanceled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive the main game tick event
|
||||||
|
@SubscribeEvent
|
||||||
|
public void onEvent(TickEvent.ClientTickEvent event)
|
||||||
|
{
|
||||||
|
// DISABLED for 1.3.1 -- now only warns players
|
||||||
|
// kill mod completely when a conflict is detected.
|
||||||
|
// if (this.conflictsDetected) return;
|
||||||
|
|
||||||
|
// Make this block as fail-safe as possible, since it runs every tick
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Decrement timers
|
||||||
|
if (checkEnableModDelay > 0) --checkEnableModDelay;
|
||||||
|
if (checkEnableRealArmsDelay > 0) --checkEnableRealArmsDelay;
|
||||||
|
if (suspendApiDelay > 0) --suspendApiDelay;
|
||||||
|
|
||||||
|
// Get player reference and null check it
|
||||||
|
EntityPlayer player = Minecraft.getMinecraft().player;
|
||||||
|
if (player != null)
|
||||||
|
{
|
||||||
|
// Check if dummy needs to be spawned
|
||||||
|
if (dummy == null)
|
||||||
|
{
|
||||||
|
// It does, are we in a respawn waiting interval?
|
||||||
|
if (spawnDelay > 0)
|
||||||
|
{
|
||||||
|
// Yes, we are still waiting; is the mod enabled?
|
||||||
|
if (enableMod)
|
||||||
|
{
|
||||||
|
// Yes, the mod is enabled and we are waiting: decrement the counter
|
||||||
|
--spawnDelay;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No, the mod is not enabled, and we are waiting:
|
||||||
|
// Hold the timer at full so that the delay works when the mod is turned back on
|
||||||
|
spawnDelay = RFP2.DUMMY_MIN_RESPAWN_INTERVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No, the spawn timer has expired: Go ahead and try to spawn the dummy.
|
||||||
|
attemptDummySpawn(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The dummy already exists, let's check up on it
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Track whether we need to reset the existing dummy.
|
||||||
|
// We should only reset it ONCE, even if multiple reasons are true.
|
||||||
|
// (otherwise we will not be able to log the remaining reasons after it is reset)
|
||||||
|
// This is done this way to ease future troubleshooting.
|
||||||
|
boolean needsReset = false;
|
||||||
|
|
||||||
|
// Did the player change dimensions on us? If so, reset the dummy.
|
||||||
|
if (dummy.world.provider.getDimension() != player.world.provider.getDimension())
|
||||||
|
{
|
||||||
|
needsReset = true;
|
||||||
|
RFP2.logger.log(RFP2.LOGGING_LEVEL_DEBUG,
|
||||||
|
this.getClass().getName() + ": Respawning dummy because player changed dimension.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did the player teleport, move too fast, or somehow else get separated? If so, reset the dummy.
|
||||||
|
if (dummy.getDistanceSq(player) > RFP2.DUMMY_MAX_SEPARATION)
|
||||||
|
{
|
||||||
|
needsReset = true;
|
||||||
|
RFP2.logger.log(RFP2.LOGGING_LEVEL_DEBUG,
|
||||||
|
this.getClass().getName() + ": Respawning dummy because player and dummy became separated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has it been excessively long since we last updated the dummy's state? (perhaps due to lag?)
|
||||||
|
if (dummy.lastTickUpdated < player.world.getTotalWorldTime() - RFP2.DUMMY_UPDATE_TIMEOUT)
|
||||||
|
{
|
||||||
|
needsReset = true;
|
||||||
|
RFP2.logger.log(RFP2.LOGGING_LEVEL_DEBUG,
|
||||||
|
this.getClass().getName() + ": Respawning dummy because state became stale. (Is the server lagging?)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did one of the above checks necessitate a reset?
|
||||||
|
if (needsReset)
|
||||||
|
{
|
||||||
|
// Yes, proceed with the reset.
|
||||||
|
resetDummy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// If anything goes wrong, shut the mod off and write an error to the logs.
|
||||||
|
RFP2.errorDisableMod(this.getClass().getName() + ".onEvent(TickEvent.ClientTickEvent)", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles dummy spawning
|
||||||
|
void attemptDummySpawn(EntityPlayer player)
|
||||||
|
{
|
||||||
|
// Only runs once per startup. Running it here at dummy spawn is the easiest way to ensure it only happens after everything is fully loaded.
|
||||||
|
detectModConflicts(player);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Make sure any existing dummy is dead
|
||||||
|
if (dummy != null) dummy.setDead();
|
||||||
|
|
||||||
|
// Attempt to spawn a new one at the player's current position
|
||||||
|
dummy = new EntityPlayerDummy(player.world);
|
||||||
|
dummy.setPositionAndRotation(player.posX, player.posY, player.posZ, player.rotationYaw, player.rotationPitch);
|
||||||
|
player.world.spawnEntity(dummy);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Something went wrong trying to spawn the dummy!
|
||||||
|
* We need to write a log entry and reschedule to try again later.
|
||||||
|
*
|
||||||
|
* Note that because this code is protected against running too much by a respawn timer,
|
||||||
|
* we do not call errorDisableMod() when encountering this error.
|
||||||
|
*
|
||||||
|
* Should anything unexpected occur in the spawning, there is a good chance that it will
|
||||||
|
* work itself out within a respawn delay or two.
|
||||||
|
*/
|
||||||
|
RFP2.logger.log(RFP2.LOGGING_LEVEL_MED, this.getClass().getName() + ": failed to spawn PlayerDummy! Will retry. Exception:", e.toString());
|
||||||
|
e.printStackTrace();
|
||||||
|
resetDummy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles killing off defunct dummies and scheduling respawns
|
||||||
|
void resetDummy()
|
||||||
|
{
|
||||||
|
// If the existing dummy isn't dead, kill it before freeing the reference
|
||||||
|
if (dummy != null) dummy.setDead();
|
||||||
|
dummy = null;
|
||||||
|
|
||||||
|
// DISABLED for 1.3.1 -- now only warns players
|
||||||
|
// kill mod completely when a conflict is detected.
|
||||||
|
// if (this.conflictsDetected) return;
|
||||||
|
|
||||||
|
// Set timer to spawn a new one
|
||||||
|
spawnDelay = RFP2.DUMMY_MIN_RESPAWN_INTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuspendTimer(int ticks)
|
||||||
|
{
|
||||||
|
// DISABLED for 1.3.1 -- now only warns players
|
||||||
|
// kill mod completely when a conflict is detected.
|
||||||
|
// if (this.conflictsDetected) return;
|
||||||
|
|
||||||
|
// check if tick value is valid; invalid values will be ignored
|
||||||
|
if (ticks > 0 && ticks <= RFP2.MAX_SUSPEND_TIMER)
|
||||||
|
{
|
||||||
|
// Only allow increasing the timer externally
|
||||||
|
// * This is so multiple mods can use the API concurrently, and RFP2 being suspended is the preferred state.
|
||||||
|
// * Once all mods stop requesting suspension times, the timer will expire at the longest, last value requested.
|
||||||
|
if (ticks > suspendApiDelay) suspendApiDelay = ticks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if mod should be disabled for any reason
|
||||||
|
public boolean isModEnabled(EntityPlayer player)
|
||||||
|
{
|
||||||
|
// DISABLED for 1.3.1 -- now only warns players
|
||||||
|
// kill mod completely when a conflict is detected.
|
||||||
|
// if (this.conflictsDetected) return;
|
||||||
|
|
||||||
|
// No need to check anything if we are configured to be disabled
|
||||||
|
if (!enableMod) return false;
|
||||||
|
|
||||||
|
// Don't do anything if we've been suspended by another mod
|
||||||
|
if (suspendApiDelay > 0) return false;
|
||||||
|
|
||||||
|
// No need to check anything else if player is dead or otherwise cannot be found
|
||||||
|
if (player == null) return false;
|
||||||
|
|
||||||
|
// No need to check anything else if dummy is dead or otherwise cannot be found
|
||||||
|
if (dummy == null) return false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only check the player's riding status if we haven't recently.
|
||||||
|
* This saves on performance -- it is not necessary to check this list on every single frame!
|
||||||
|
* Once every few ticks is more than enough to remain invisible to the player.
|
||||||
|
* Keep in mind that "every few ticks", or several 20ths of a second,
|
||||||
|
* could be tens of frames where we skip the check with a good GPU!
|
||||||
|
*/
|
||||||
|
if (checkEnableModDelay == 0)
|
||||||
|
{
|
||||||
|
// The timer has expired, we need to run the checks
|
||||||
|
|
||||||
|
// reset timer
|
||||||
|
checkEnableModDelay = RFP2.MIN_ACTIVATION_CHECK_INTERVAL;
|
||||||
|
|
||||||
|
// Implement swimming check functionality
|
||||||
|
if (RFP2Config.compatibility.disableWhenSwimming && dummy.isSwimming())
|
||||||
|
{
|
||||||
|
// we are swimming and are configured to disable when this is true, so we are disabled
|
||||||
|
lastActivateCheckResult = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we are not swimming, or that check is disabled. proceed to the mount check
|
||||||
|
|
||||||
|
// get a reference to the player's mount, if it exists
|
||||||
|
Entity playerMountEntity = player.getRidingEntity();
|
||||||
|
if (playerMountEntity == null)
|
||||||
|
{
|
||||||
|
// Player isn't riding, so we are enabled.
|
||||||
|
lastActivateCheckResult = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Player is riding something, find out what it is and if it's on our conflict list
|
||||||
|
if (stringMatchesRegexList(playerMountEntity.getName().toLowerCase(), RFP2Config.compatibility.mountConflictList))
|
||||||
|
{
|
||||||
|
// player is riding a conflicting entity, so we are disabled.
|
||||||
|
lastActivateCheckResult = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No conflicts found, so we are enabled.
|
||||||
|
lastActivateCheckResult = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return lastActivateCheckResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should render real arms or not
|
||||||
|
public boolean isRealArmsEnabled(EntityPlayer player)
|
||||||
|
{
|
||||||
|
// DISABLED for 1.3.1 -- now only warns players
|
||||||
|
// kill mod completely when a conflict is detected.
|
||||||
|
// if (this.conflictsDetected) return;
|
||||||
|
|
||||||
|
// No need to check anything if we don't want this enabled
|
||||||
|
if (!enableRealArms) return false;
|
||||||
|
|
||||||
|
// No need to check anything if player is dead
|
||||||
|
if (player == null) return false;
|
||||||
|
|
||||||
|
// only run the inventory check if we haven't done it recently
|
||||||
|
// once per tick is enough -- isRealArmsEnabled might be called many times per tick!
|
||||||
|
if (checkEnableRealArmsDelay == 0)
|
||||||
|
{
|
||||||
|
// need to check the player's inventory after all
|
||||||
|
// reset the check timer
|
||||||
|
checkEnableRealArmsDelay = RFP2.MIN_REAL_ARMS_CHECK_INTERVAL;
|
||||||
|
|
||||||
|
// get the names of the player's currently held items
|
||||||
|
String itemMainHand = player.inventory.getCurrentItem().getItem().getRegistryName().toString().toLowerCase();
|
||||||
|
String itemOffHand = player.inventory.offHandInventory.get(0).getItem().getRegistryName().toString().toLowerCase();
|
||||||
|
|
||||||
|
// Modify the check logic based on whether the "any item" flag is set or not
|
||||||
|
if (RFP2Config.compatibility.disableArmsWhenAnyItemHeld)
|
||||||
|
{
|
||||||
|
// "any item held" behavior is enabled; check if player's hands are empty
|
||||||
|
if (itemMainHand.equals("minecraft:air") && itemOffHand.equals("minecraft:air"))
|
||||||
|
{
|
||||||
|
// player is not holding anything; enable arm rendering
|
||||||
|
lastRealArmsCheckResult = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// player is holding something; disable arm rendering
|
||||||
|
lastRealArmsCheckResult = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The "any item" option is not in use, so we need to check the registry names of any
|
||||||
|
// held items against the conflict list
|
||||||
|
if (stringMatchesRegexList(itemMainHand, RFP2Config.compatibility.heldItemConflictList)
|
||||||
|
|| (stringMatchesRegexList(itemOffHand, RFP2Config.compatibility.heldItemConflictList)))
|
||||||
|
{
|
||||||
|
// player is holding a conflicting item in main or off hand; disable arm rendering
|
||||||
|
lastRealArmsCheckResult = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no conflicts found; enable arm rendering
|
||||||
|
lastRealArmsCheckResult = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastRealArmsCheckResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if head rotation is enabled
|
||||||
|
public boolean isHeadRotationEnabled(EntityPlayer player)
|
||||||
|
{
|
||||||
|
// DISABLED for 1.3.1 -- now only warns players
|
||||||
|
// kill mod completely when a conflict is detected.
|
||||||
|
// if (this.conflictsDetected) return;
|
||||||
|
|
||||||
|
return enableHeadTurning;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check a string against a list of regexes and return true if any of them match
|
||||||
|
boolean stringMatchesRegexList(String string, String[] regexes)
|
||||||
|
{
|
||||||
|
// Loop through regex array
|
||||||
|
for (String i : regexes)
|
||||||
|
{
|
||||||
|
// Handle errors due to bad regex syntax entered by user
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if the provided string matches the regex
|
||||||
|
if (string.matches(i))
|
||||||
|
{
|
||||||
|
// Found a hit, return true
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (PatternSyntaxException e)
|
||||||
|
{
|
||||||
|
// Something is wrong with the regex, switch off the mod and notify the user
|
||||||
|
enableMod = false;
|
||||||
|
RFP2.logToChat(RFP2.MODNAME + " " + TextFormatting.RED + "Warning: [ " + i + " ] is not a valid regex, please edit your configuration.");
|
||||||
|
RFP2.logToChat(RFP2.MODNAME + " mod " + TextFormatting.RED + " disabled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Got through the whole array without a hit; return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
295
src/main/java/com/rejahtavi/rfp2/RenderPlayerDummy.java
Normal file
295
src/main/java/com/rejahtavi/rfp2/RenderPlayerDummy.java
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
package com.rejahtavi.rfp2;
|
||||||
|
|
||||||
|
import com.rejahtavi.rfp2.compat.handlers.RFP2CompatHandler;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.entity.AbstractClientPlayer;
|
||||||
|
import net.minecraft.client.entity.EntityPlayerSP;
|
||||||
|
import net.minecraft.client.model.ModelPlayer;
|
||||||
|
import net.minecraft.client.renderer.entity.Render;
|
||||||
|
import net.minecraft.client.renderer.entity.RenderManager;
|
||||||
|
import net.minecraft.client.renderer.entity.RenderPlayer;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class handles calls to draw the PlayerDummy object (except not really.)
|
||||||
|
*
|
||||||
|
* When doRender() is called, we don't draw anything for the dummy itself.
|
||||||
|
* Instead, we prepare some things, then call a vanilla player renderer at a very precise position relative to the camera.
|
||||||
|
*
|
||||||
|
* In other words: The original doRender() call is *indirectly* triggering a vanilla player render operation,
|
||||||
|
* instead of rendering our dummy entity (which is invisible anyway).
|
||||||
|
*
|
||||||
|
* Using the vanilla renderer means we will automatically inherit any changes *other* mods have made to the player character,
|
||||||
|
* but it also means that we have to deal with anything they add to the head that could block the view.
|
||||||
|
*/
|
||||||
|
public class RenderPlayerDummy extends Render<EntityPlayerDummy>
|
||||||
|
{
|
||||||
|
// Constructor
|
||||||
|
public RenderPlayerDummy(RenderManager renderManager)
|
||||||
|
{
|
||||||
|
// Call parent constructor
|
||||||
|
super(renderManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles requests for texture of the player dummy
|
||||||
|
@Override
|
||||||
|
protected ResourceLocation getEntityTexture(EntityPlayerDummy entity)
|
||||||
|
{
|
||||||
|
// The PlayerDummy entity is only for tracking the player's position, so it does not have a texture.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements linear interpolation for animation smoothing
|
||||||
|
private float linearInterpolate(float current, float target, float partialTicks)
|
||||||
|
{
|
||||||
|
// Explanation for linear interpolation math can be found here:
|
||||||
|
// https://en.wikipedia.org/wiki/Linear_interpolation
|
||||||
|
return ((1 - partialTicks) * current) + (partialTicks * target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the game wants to draw our PlayerDummy entity
|
||||||
|
@Override
|
||||||
|
public void doRender(EntityPlayerDummy renderEntity, double renderPosX, double renderPosY, double renderPosZ, float renderYaw, float partialTicks)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* NO-OP Checklist: We want to use as few CPU cycles as possible if we aren't
|
||||||
|
* going to do anything useful. The following checks abort the render *as soon
|
||||||
|
* as possible* if any of those conditions are true.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Grab a reference to the local player entity, null-check it, and abort if it fails.
|
||||||
|
EntityPlayerSP player = Minecraft.getMinecraft().player;
|
||||||
|
if (player == null) return;
|
||||||
|
|
||||||
|
// Grab a backup of any items we might possibly touch, so that we can be guaranteed
|
||||||
|
// to be able to restore them when it comes time for the finally{} block to run.
|
||||||
|
ItemStack itemMainHand = player.inventory.getCurrentItem();
|
||||||
|
ItemStack itemOffHand = player.inventory.offHandInventory.get(0);
|
||||||
|
ItemStack itemHelmetSlot = player.inventory.armorInventory.get(3);
|
||||||
|
|
||||||
|
// Make quick per-frame compatibility checks based on current configuration and player state
|
||||||
|
|
||||||
|
// Implement config option for disabling when sneaking
|
||||||
|
if (RFP2Config.compatibility.disableWhenSneaking && player.isSneaking()) return;
|
||||||
|
|
||||||
|
// Grab a reference to the vanilla player renderer, null check, and abort if it fails
|
||||||
|
Render<AbstractClientPlayer> render = (RenderPlayer) this.renderManager.<AbstractClientPlayer>getEntityRenderObject(player);
|
||||||
|
RenderPlayer playerRenderer = (RenderPlayer) render;
|
||||||
|
if (playerRenderer == null) return;
|
||||||
|
|
||||||
|
// Grab a reference to the local player's model, null check, and abort if it fails
|
||||||
|
ModelPlayer playerModel = (ModelPlayer) playerRenderer.getMainModel();
|
||||||
|
if (playerModel == null) return;
|
||||||
|
|
||||||
|
RFP2.logger.log(RFP2.LOGGING_LEVEL_HIGH, playerModel.getClass().getCanonicalName());
|
||||||
|
|
||||||
|
// Grab a backup of the various player model layers we might adjust
|
||||||
|
// This way we aren't making any assumptions about what other mods might be doing with these options
|
||||||
|
// and we can restore everything when we are finished.
|
||||||
|
boolean[] modelState = { playerModel.bipedHead.isHidden,
|
||||||
|
playerModel.bipedHead.showModel,
|
||||||
|
playerModel.bipedHeadwear.isHidden,
|
||||||
|
playerModel.bipedHeadwear.showModel,
|
||||||
|
playerModel.bipedLeftArm.isHidden,
|
||||||
|
playerModel.bipedLeftArm.showModel,
|
||||||
|
playerModel.bipedLeftArmwear.isHidden,
|
||||||
|
playerModel.bipedLeftArmwear.showModel,
|
||||||
|
playerModel.bipedRightArm.isHidden,
|
||||||
|
playerModel.bipedRightArm.showModel,
|
||||||
|
playerModel.bipedRightArmwear.isHidden,
|
||||||
|
playerModel.bipedRightArmwear.showModel
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* With the routine, unlikely-to-fail stuff out of the way, try to make the remainder
|
||||||
|
* of this routine as fail-safe as possible. It runs every frame, so we want to be able
|
||||||
|
* to stop it from running anymore if we encounter a problem, to avoid slowing down the
|
||||||
|
* game and consuming disk space with useless error logs.
|
||||||
|
*/
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Note: thirdPersonView can be: 0 = First Person, 1 = Third Person Rear, 2 = Third Person
|
||||||
|
// If the player is NOT in first person, do nothing
|
||||||
|
if (Minecraft.getMinecraft().gameSettings.thirdPersonView != 0) return;
|
||||||
|
|
||||||
|
// If the player is flying with an Elytra, do nothing
|
||||||
|
if (player.isElytraFlying()) return;
|
||||||
|
|
||||||
|
// (Keep this NO-OP check last, it can be more expensive than the others, due to the mount check.)
|
||||||
|
// If mod is not enabled this frame, do nothing
|
||||||
|
if (!RFP2.state.isModEnabled(player)) return;
|
||||||
|
if (!RFP2.state.isRealArmsEnabled(player)) return;
|
||||||
|
|
||||||
|
// Check if any of the compatibility handlers want us to skip this frame
|
||||||
|
for (RFP2CompatHandler handler : RFP2.compatHandlers)
|
||||||
|
{
|
||||||
|
if (handler.getDisableRFP2(player))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialization: Pull in state info and set up local variables, now that we
|
||||||
|
* know we actually need to do some rendering.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Initialize remaining local variables
|
||||||
|
double playerRenderPosX = 0;
|
||||||
|
double playerRenderPosZ = 0;
|
||||||
|
double playerRenderPosY = 0;
|
||||||
|
float playerRenderAngle = 0;
|
||||||
|
|
||||||
|
// Get local copies of config & state
|
||||||
|
float playerModelOffset = (float) RFP2Config.preferences.playerModelOffset;
|
||||||
|
boolean isRealArmsEnabled = RFP2.state.isRealArmsEnabled(player);
|
||||||
|
boolean isHeadRotationEnabled = RFP2.state.isHeadRotationEnabled(player);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adjust Player Model:
|
||||||
|
* Strip unwanted items and layers from the player model to avoid obstructing the camera
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Remove the player's helmet, so that it does not obstruct the camera.
|
||||||
|
player.inventory.armorInventory.set(3, ItemStack.EMPTY);
|
||||||
|
|
||||||
|
// Hide the player model's head layers, again so they do not obstruct the camera.
|
||||||
|
playerModel.bipedHead.isHidden = true;
|
||||||
|
playerModel.bipedHead.showModel = false;
|
||||||
|
playerModel.bipedHeadwear.isHidden = true;
|
||||||
|
playerModel.bipedHeadwear.showModel = false;
|
||||||
|
|
||||||
|
// Instruct compatibility handlers hide head models (handlers are responsible for caching state for later restoration)
|
||||||
|
for (RFP2CompatHandler handler : RFP2.compatHandlers)
|
||||||
|
{
|
||||||
|
handler.hideHead(player, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we need to hide the arms
|
||||||
|
if (!isRealArmsEnabled)
|
||||||
|
{
|
||||||
|
// The real arms feature is not enabled, so we should NOT render the arms in 3D.
|
||||||
|
// That means we need to hide them before we draw the player model.
|
||||||
|
// Remove the player's currently held main and off hand items, so that they do not obstruct the camera.
|
||||||
|
player.inventory.removeStackFromSlot(player.inventory.currentItem);
|
||||||
|
player.inventory.offHandInventory.set(0, ItemStack.EMPTY);
|
||||||
|
|
||||||
|
// Hide the player model's arm layers
|
||||||
|
playerModel.bipedLeftArm.isHidden = true;
|
||||||
|
playerModel.bipedLeftArm.showModel = false;
|
||||||
|
playerModel.bipedRightArm.isHidden = true;
|
||||||
|
playerModel.bipedRightArm.showModel = false;
|
||||||
|
playerModel.bipedLeftArmwear.isHidden = true;
|
||||||
|
playerModel.bipedLeftArmwear.showModel = false;
|
||||||
|
playerModel.bipedRightArmwear.isHidden = true;
|
||||||
|
playerModel.bipedRightArmwear.showModel = false;
|
||||||
|
|
||||||
|
// Instruct compatibility handlers hide arm models (handlers are responsible for caching state for later restoration)
|
||||||
|
for (RFP2CompatHandler handler : RFP2.compatHandlers)
|
||||||
|
{
|
||||||
|
handler.hideArms(player, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate Rendering Coordinates:
|
||||||
|
* Determine the precise location and angle the player should be rendered this frame, then render it.
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* player.rotationYaw = The direction the player's camera is facing.
|
||||||
|
* player.renderYawOffset = The direction the player's 3D model is facing.
|
||||||
|
*
|
||||||
|
* (In vanilla minecraft, the player's body lags behind the head to provide a more natural look to movement.
|
||||||
|
* This can lead to renderYawOffset being off from the main camera by up to 75 degrees!)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Generate default rendering coordinates for player body
|
||||||
|
playerRenderPosX = player.posX - renderEntity.posX + renderPosX;
|
||||||
|
playerRenderPosY = player.posY - renderEntity.posY + renderPosY;
|
||||||
|
playerRenderPosZ = player.posZ - renderEntity.posZ + renderPosZ;
|
||||||
|
|
||||||
|
// If the player IS sleeping, we can skip any extra calculations and proceed directly to rendering.
|
||||||
|
if (!player.isPlayerSleeping())
|
||||||
|
{
|
||||||
|
// The player is NOT sleeping, so we are going to need to make some adjustments.
|
||||||
|
// If head rotation IS enabled, then we DO NOT need to counteract it and can skip the next adjustment.
|
||||||
|
if (!isHeadRotationEnabled)
|
||||||
|
{
|
||||||
|
// Head rotation is NOT enabled, so we have to prevent the vanilla rotation behavior!
|
||||||
|
// We can do this by updating the player's body's rendering values to match the camera's
|
||||||
|
// position before rendering each frame. It is *critical* that we update the data for both
|
||||||
|
// this frame and the previous one, or else our linear interpolation will be fed bad
|
||||||
|
// data and the result will not look smooth!
|
||||||
|
player.renderYawOffset = player.rotationYaw;
|
||||||
|
player.prevRenderYawOffset = player.prevRotationYaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate to get final rendering position
|
||||||
|
playerRenderAngle = this.linearInterpolate(player.prevRenderYawOffset, player.renderYawOffset, partialTicks);
|
||||||
|
|
||||||
|
// Update position of rendered body to include interpolation and model offset
|
||||||
|
playerRenderPosX += (playerModelOffset * Math.sin(Math.toRadians(playerRenderAngle)));
|
||||||
|
playerRenderPosZ -= (playerModelOffset * Math.cos(Math.toRadians(playerRenderAngle)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trigger rendering of the fake player model: this is done by calling the
|
||||||
|
* vanilla player renderer with our adjusted coordinates.
|
||||||
|
*
|
||||||
|
* Note that we do NOT pass the playerModel here -- the vanilla renderer already
|
||||||
|
* has it! That's why we had to actually remove the player's helmet and possibly
|
||||||
|
* other inventory items. That also means that it is *critical* that we undo all
|
||||||
|
* of those changes immediately after this, before anything outside of RFP2 can
|
||||||
|
* interact with these objects and invalidate our cached state.
|
||||||
|
*
|
||||||
|
* (That is also why those reversions are implemented within a finally block.)
|
||||||
|
*/
|
||||||
|
playerRenderer.doRender(player, playerRenderPosX, playerRenderPosY, playerRenderPosZ, playerRenderAngle, partialTicks);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// If anything goes wrong, shut the mod off and write an error to the logs.
|
||||||
|
RFP2.errorDisableMod(this.getClass().getName() + ".doRender()", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
/*false
|
||||||
|
* Cleanup Phase:
|
||||||
|
* Revert all temporary changes we made to the model for rendering purposes, so that we don't cause any side effects.
|
||||||
|
*
|
||||||
|
* Whether or not something went wrong, we want to make ABSOLUTELY SURE to give
|
||||||
|
* back any items we took and re-enable all player model rendering layers.
|
||||||
|
*
|
||||||
|
* If we fail to do this, we could glitch out the player's rendering, or worse,
|
||||||
|
* accidentally delete someone's hard won inventory items!
|
||||||
|
*/
|
||||||
|
|
||||||
|
// restore the player's inventory to the state we found it in
|
||||||
|
player.inventory.armorInventory.set(3, itemHelmetSlot);
|
||||||
|
player.inventory.setInventorySlotContents(player.inventory.currentItem, itemMainHand);
|
||||||
|
player.inventory.offHandInventory.set(0, itemOffHand);
|
||||||
|
|
||||||
|
// restore the player model's rendering layers
|
||||||
|
playerModel.bipedHead.isHidden = modelState[0];
|
||||||
|
playerModel.bipedHead.showModel = modelState[1];
|
||||||
|
playerModel.bipedHeadwear.isHidden = modelState[2];
|
||||||
|
playerModel.bipedHeadwear.showModel = modelState[3];
|
||||||
|
playerModel.bipedLeftArm.isHidden = modelState[4];
|
||||||
|
playerModel.bipedLeftArm.showModel = modelState[5];
|
||||||
|
playerModel.bipedLeftArmwear.isHidden = modelState[6];
|
||||||
|
playerModel.bipedLeftArmwear.showModel = modelState[7];
|
||||||
|
playerModel.bipedRightArm.isHidden = modelState[8];
|
||||||
|
playerModel.bipedRightArm.showModel = modelState[9];
|
||||||
|
playerModel.bipedRightArmwear.isHidden = modelState[10];
|
||||||
|
playerModel.bipedRightArmwear.showModel = modelState[11];
|
||||||
|
|
||||||
|
// Instruct compatibility handlers restore head models
|
||||||
|
for (RFP2CompatHandler handler : RFP2.compatHandlers)
|
||||||
|
{
|
||||||
|
handler.restoreHead(player, true);
|
||||||
|
handler.restoreArms(player, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
src/main/java/com/rejahtavi/rfp2/ServerProxy.java
Normal file
39
src/main/java/com/rejahtavi/rfp2/ServerProxy.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package com.rejahtavi.rfp2;
|
||||||
|
|
||||||
|
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||||
|
import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
|
||||||
|
|
||||||
|
// RFP2.PROXY will be instantiated with this class if we are running on the server side.
|
||||||
|
// When this is the case, RFP2 should do nothing, so there is nothing here.
|
||||||
|
public class ServerProxy implements IProxy
|
||||||
|
{
|
||||||
|
// Called at the start of mod loading
|
||||||
|
@Override
|
||||||
|
public void preInit(FMLPreInitializationEvent event)
|
||||||
|
{
|
||||||
|
// unused in this mod
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called after all other mod preInit()s have run
|
||||||
|
@Override
|
||||||
|
public void init(FMLInitializationEvent event)
|
||||||
|
{
|
||||||
|
// unused in this mod
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called after all other mod init()s have run
|
||||||
|
@Override
|
||||||
|
public void postInit(FMLPostInitializationEvent event)
|
||||||
|
{
|
||||||
|
// unused in this mod
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when starting up a dedicated server
|
||||||
|
@Override
|
||||||
|
public void serverStarting(FMLServerStartingEvent event)
|
||||||
|
{
|
||||||
|
// unused in this mod
|
||||||
|
}
|
||||||
|
}
|
50
src/main/java/com/rejahtavi/rfp2/compat/RFP2CompatApi.java
Normal file
50
src/main/java/com/rejahtavi/rfp2/compat/RFP2CompatApi.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package com.rejahtavi.rfp2.compat;
|
||||||
|
|
||||||
|
import com.rejahtavi.rfp2.RFP2;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.entity.EntityPlayerSP;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compatibility API other mods can use to get the current state of RFP2.
|
||||||
|
*
|
||||||
|
* If you have a mod that modifies the player model or player rendering in any way,
|
||||||
|
* you can call the functions below to determine whether you should hide certain things.
|
||||||
|
*/
|
||||||
|
public class RFP2CompatApi
|
||||||
|
{
|
||||||
|
|
||||||
|
// During frames that this returns TRUE:
|
||||||
|
// * RFP2 has hidden the player's head and helmet so that they don't block the first person view camera.
|
||||||
|
// * Make sure to adjust your mod's behavior to avoid rendering anything that could block the forward field of view.
|
||||||
|
public boolean rfp2IsHeadHidden()
|
||||||
|
{
|
||||||
|
EntityPlayerSP player = Minecraft.getMinecraft().player;
|
||||||
|
if (player == null) return false;
|
||||||
|
return RFP2.state.isModEnabled(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// During frames that this returns TRUE:
|
||||||
|
// * RFP2 has hidden the third person model's arms and is currently allowing "RenderHandEvent" to run normally.
|
||||||
|
// * This means that the player is in first person view, but is using the vanilla first person hands instead of the RFP2 third person hands.
|
||||||
|
// * Make sure you adjust your mod's rendering behavior accordingly as needed.
|
||||||
|
// During frames that this returns FALSE:
|
||||||
|
// * RFP2 has NOT hidden the third person model's arms.
|
||||||
|
// * In most cases you should be able to render arms normally in this state.
|
||||||
|
public boolean rfp2AreThirdPersonArmsHidden()
|
||||||
|
{
|
||||||
|
EntityPlayerSP player = Minecraft.getMinecraft().player;
|
||||||
|
if (player == null) return false;
|
||||||
|
return !RFP2.state.isRealArmsEnabled(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you are making a mod that for some reason needs to temporarily suspend RFP2, you can use the following call to do so.
|
||||||
|
// You cannot exceed RFP2.MAX_SUSPEND_TIMER ticks when calling this, if you need to keep RFP2 suspended for longer,
|
||||||
|
// you will have to call this function at least once every RFP2.MAX_SUSPEND_TIMER ticks to keep it suspended.
|
||||||
|
// You cannot decrease the timer through this method, so it is a good idea to set it as short as will work for your use case.
|
||||||
|
// The idea here is that multiple mods can all be calling this function to suspend RFP2, and only once all mods have
|
||||||
|
// stopped making requests will RFP2 allow itself to start back up again.
|
||||||
|
public void rfp2AddSuspendTime(int ticks)
|
||||||
|
{
|
||||||
|
RFP2.state.setSuspendTimer(ticks);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package com.rejahtavi.rfp2.compat.handlers;
|
||||||
|
|
||||||
|
import net.minecraft.entity.player.EntityPlayer;
|
||||||
|
|
||||||
|
//compatibility module base class / template
|
||||||
|
public class RFP2CompatHandler
|
||||||
|
{
|
||||||
|
// Mod Info Template
|
||||||
|
// public static final String modId = "";
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
public RFP2CompatHandler()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Behavior Getter Templates
|
||||||
|
public boolean getDisableRFP2(EntityPlayer player)
|
||||||
|
{
|
||||||
|
// By default, do nothing unless overridden.
|
||||||
|
// @Overrides should return TRUE if they want RFP2 to completely skip on the current frame, letting vanilla rendering take over.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Behavior Setter Templates
|
||||||
|
public void hideHead(EntityPlayer player, boolean hideHelmet)
|
||||||
|
{
|
||||||
|
// By default, do nothing unless overridden.
|
||||||
|
// @Overrides should call isHeadHidden(), store the result into prevHeadHiddenState, then hide all head objects.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideArms(EntityPlayer player, boolean hideHelmet)
|
||||||
|
{
|
||||||
|
// By default, do nothing unless overridden.
|
||||||
|
// @Overrides should call areArmsHidden(), store the result into prevArmsHiddenState, then hide all arm objects.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restoreHead(EntityPlayer player, boolean hideHelmet)
|
||||||
|
{
|
||||||
|
// By default, do nothing unless overridden.
|
||||||
|
// @Overrides should read prevHeadHiddenState, and if it is FALSE, restore all head object visibility.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restoreArms(EntityPlayer player, boolean hideHelmet)
|
||||||
|
{
|
||||||
|
// By default, do nothing unless overridden.
|
||||||
|
// @Overrides should read prevArmsHiddenState, and if it is FALSE, restore all arm object visibility.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
6
src/main/resources/assets/rfp2/lang/en_us.lang
Normal file
6
src/main/resources/assets/rfp2/lang/en_us.lang
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
key.rfp2.category=Real First Person 2
|
||||||
|
key.arms.desc=Toggle arm mode
|
||||||
|
key.mod.desc=Toggle mod
|
||||||
|
key.head.desc=Toggle head turning
|
||||||
|
rfp2.compatability.helditemconflictlist=Held item conflict list
|
||||||
|
rfp2.compatability.mountconflictlist=Mount conflict list
|
@ -1,14 +1,14 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"modid": "example",
|
"modid": "rfp2",
|
||||||
"name": "Example",
|
"name": "Real First Person 2",
|
||||||
"description": "",
|
"description": "Provides full body rendering in first person view.",
|
||||||
"version": "${version}",
|
"version": "${version}",
|
||||||
"mcversion": "${mcversion}",
|
"mcversion": "1.12.2",
|
||||||
"url": "",
|
"url": "https://github.com/rejahtavi/rfp2",
|
||||||
"updateUrl": "",
|
"updateUrl": "",
|
||||||
"authorList": ["PIVODEVAT"],
|
"authorList": ["Rejah Tavi", "don_bruce"],
|
||||||
"credits": "",
|
"credits": "don_bruce, for the original RFPR mod",
|
||||||
"logoFile": "",
|
"logoFile": "",
|
||||||
"screenshots": [],
|
"screenshots": [],
|
||||||
"dependencies": []
|
"dependencies": []
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pack": {
|
"pack": {
|
||||||
"description": "examplemod resources",
|
"description": "rfp2",
|
||||||
"pack_format": 3,
|
"pack_format": 3,
|
||||||
"_comment": "A pack_format of 3 should be used starting with Minecraft 1.11. All resources, including language files, should be lowercase (eg: en_us.lang). A pack_format of 2 will load your mod resources with LegacyV2Adapter, which requires language files to have uppercase letters (eg: en_US.lang)."
|
"_comment": "Provides full body rendering in first person view."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user