This commit is contained in:
OpexHunter 2025-02-18 16:32:02 +03:00
parent 7e9ac0866a
commit 101bb0984c
17 changed files with 1609 additions and 124 deletions

View File

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

View File

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

View File

@ -1,112 +0,0 @@
package com.punkcraft.punkapi;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import io.netty.buffer.Unpooled;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.entity.ai.attributes.IAttributeInstance;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.client.Minecraft;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.play.client.CPacketCustomPayload;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.common.network.FMLNetworkEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.io.File;
@Mod(modid = PunkAPI.MODID, name = PunkAPI.NAME, version = PunkAPI.VERSION)
public class PunkAPI {
public static final String MODID = "punkapi";
public static final String NAME = "PunkAPI";
public static final String VERSION = "1.0";
private String token;
private ItemStack prevMainHandItem = ItemStack.EMPTY;
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
token = System.getProperty("token");
String c = System.getProperty("c");
if (c == null || !c.equals("ea42ba5b1a35b89e628e07f881198144")) {
// Minecraft.getMinecraft().shutdown();
}
MinecraftForge.EVENT_BUS.register(this);
File minecraftDir = Minecraft.getMinecraft().mcDataDir.getAbsoluteFile().getParentFile();
String directoryName = minecraftDir.getName();
if (!"ZombieExtrieme".equals(directoryName)) {
Minecraft.getMinecraft().shutdown();
}
}
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
}
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void onClientConnected(FMLNetworkEvent.ClientConnectedToServerEvent event) {
NetworkManager networkManager = event.getManager();
sendToken(networkManager);
}
@SubscribeEvent
public void onClientItemChange(TickEvent.ClientTickEvent event) {
EntityPlayerSP player = Minecraft.getMinecraft().player;
if (player == null) {
return;
}
ItemStack currentMainHandItem = player.getHeldItemMainhand();
if (!ItemStack.areItemStacksEqual(prevMainHandItem, currentMainHandItem)) {
onItemChange(player, currentMainHandItem);
prevMainHandItem = currentMainHandItem.copy();
}
}
private void onItemChange(EntityPlayer player, ItemStack newItem) {
IAttributeInstance attribute = player.getEntityAttribute(EntityPlayer.REACH_DISTANCE);
if (newItem.getItem() instanceof ItemSword) {
attribute.setBaseValue(1.17);
} else if (newItem.getItem() == Items.WOODEN_AXE) {
attribute.setBaseValue(0);
} else {
attribute.setBaseValue(4.0);
}
}
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void onClientTick(TickEvent.ClientTickEvent event) {
Minecraft.getMinecraft().gameSettings.gammaSetting = 0.0f;
}
private void sendToken(NetworkManager networkManager) {
String playerName = Minecraft.getMinecraft().getSession().getUsername();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
//out.writeUTF(token);
out.writeUTF("ea42ba5b1a35b89e628e07f881198144");
out.writeUTF(playerName);
out.writeUTF("ea42ba5b1a35b89e628e07f881198144");
CPacketCustomPayload packet = new CPacketCustomPayload("custom:token",
new PacketBuffer(Unpooled.wrappedBuffer(out.toByteArray())));
networkManager.sendPacket(packet);
}
}

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": "pkapi", "modid": "rfp2",
"name": "PKAPI", "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": []

View File

@ -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."
} }
} }