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