Compare commits

..

1 Commits
master ... RFP2

Author SHA1 Message Date
OpexHunter
27c9842cf1 init 2025-02-19 12:24:02 +03:00
18 changed files with 1618 additions and 35 deletions

9
LICENSE.txt Normal file
View 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.

View File

@ -13,9 +13,10 @@ apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
version = '1.0'
group = 'com.yourname.modid' // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = 'modid'
version = "${mc_version}-${mod_version}"
group = 'com.rejahtavi.rfp2'
archivesBaseName = 'RealFirstPerson2'
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'
}
// Обработка ресурсов
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..
jar {
manifest {

View File

@ -3,3 +3,5 @@
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
org.gradle.java.home=/usr/lib/jvm/openjdk8
mc_version=1.12.2
mod_version=1.3.3

View File

@ -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) {
}
}

View 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
}
}

View 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)
{
}
}

View 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);
}

View 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.");
}
}
}

View 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;
}
}

View 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;
}
}
}

View 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;
}
}

View 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);
}
}
}
}

View 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
}
}

View 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);
}
}

View File

@ -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;
}
}

View 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

View File

@ -1,14 +1,14 @@
[
{
"modid": "example",
"name": "Example",
"description": "",
"modid": "rfp2",
"name": "Real First Person 2",
"description": "Provides full body rendering in first person view.",
"version": "${version}",
"mcversion": "${mcversion}",
"url": "",
"mcversion": "1.12.2",
"url": "https://github.com/rejahtavi/rfp2",
"updateUrl": "",
"authorList": ["PIVODEVAT"],
"credits": "",
"authorList": ["Rejah Tavi", "don_bruce"],
"credits": "don_bruce, for the original RFPR mod",
"logoFile": "",
"screenshots": [],
"dependencies": []

View File

@ -1,7 +1,7 @@
{
"pack": {
"description": "examplemod resources",
"description": "rfp2",
"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."
}
}