package me.km.snuviscript;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import me.hammerle.code.Code;
import me.hammerle.code.Script;
import me.hammerle.code.ScriptUtils;
import me.hammerle.code.SnuviParser;
import me.hammerle.math.Fraction;
import me.km.KajetansMod;
import me.km.api.Location;
import me.km.api.ModuleListener;
import me.km.api.Module;
import me.km.api.Utils;
import me.km.dimensions.ModDimensions;
import me.km.effects.PlayerUsesEffectEvent;
import me.km.entities.EntityHuman;
import me.km.entities.EntityItemProjectile;
import me.km.events.PlayerHurtEvent;
import me.km.events.PlayerJoinMessageEvent;
import me.km.events.PlayerLeaveMessageEvent;
import me.km.events.PlayerMoveEvent;
import me.km.events.PlayerRespawnAtEvent;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.passive.EntitySheep;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Items;
import net.minecraft.inventory.ClickType;
import net.minecraft.item.ItemStack;
import net.minecraft.server.management.PlayerList;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.WorldServer;
import net.minecraftforge.event.CommandEvent;
import net.minecraftforge.event.entity.EntityMountEvent;
import net.minecraftforge.event.entity.ThrowableImpactEvent;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.*;
import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
import net.minecraftforge.event.entity.player.FillBucketEvent;
import net.minecraftforge.event.entity.player.ItemFishedEvent;
import net.minecraftforge.event.entity.player.PlayerDropsEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;

public class ScriptEvents extends ModuleListener
{               
    private final HashMap<UUID, UUID> questJoin;
    private final SnuviParser parser;
                 
    public ScriptEvents(Module m)
    {    
        super(m);
        questJoin = new HashMap<>();
        parser = KajetansMod.scripts.getSnuviParser();
    }  
    
    // -------------------------------------------------------------------------
    // Basics
    // -------------------------------------------------------------------------
    
    private void handleEvent(EntityPlayer p, String event, Consumer<Script> before, Consumer<Script> after)
    {      
        parser.callEvent(event, (sc) -> 
        {
            if(p != null)
            {
                ScriptVars.setPlayerVars(sc, p); 
            }
            before.accept(sc);
        }, after);
        
        if(p != null)
        {
            Script data = KajetansMod.scripts.getScript(p);
            if(data != null)
            {
                parser.callEvent(event, data, (sc) -> 
                {
                    ScriptVars.setPlayerVars(sc, p); 
                    before.accept(sc);
                }, after);
            }
        }      
    }

    private void handleEvent(EntityPlayer p, String event, Consumer<Script> before)
    {      
        handleEvent(p, event, before, null);
    }
         
    // -------------------------------------------------------------------------
    // Questevents
    // -------------------------------------------------------------------------
    
    @SubscribeEvent
    public void onOtherScriptJoin(PlayerInteractEvent.EntityInteract e)
    {              
        if(e.getHand() == EnumHand.OFF_HAND || !(e.getTarget() instanceof EntityPlayer))
        {
            return;
        }
        EntityPlayer p = e.getEntityPlayer();   
        EntityPlayer affectedPlayer = (EntityPlayer) e.getTarget();
        if(KajetansMod.scripts.hasScript(p) && !KajetansMod.scripts.hasScript(affectedPlayer))
        {
            if(questJoin.get(affectedPlayer.getUniqueID()) != null)
            {
                if(questJoin.get(affectedPlayer.getUniqueID()).equals(p.getUniqueID()))
                {
                    return;
                }
            }           
            questJoin.put(affectedPlayer.getUniqueID(), p.getUniqueID());
            this.getModule().send(affectedPlayer, p.getName() + " fragt dich, ob du seiner Quest beitreten willst.");
            this.getModule().send(p, affectedPlayer.getName() + " wurde eine Anfrage gesendet.");
            return;
        }
        if(!KajetansMod.scripts.hasScript(p) && questJoin.get(p.getUniqueID()) != null && KajetansMod.scripts.hasScript(affectedPlayer))
        {
            if(questJoin.get(p.getUniqueID()).equals(affectedPlayer.getUniqueID()))
            {
                questJoin.remove(p.getUniqueID());
                KajetansMod.scripts.addPlayerToPlayer(p, affectedPlayer);
            }
        }
    }

    @SubscribeEvent
    public void onPlayerMove(PlayerMoveEvent e)
    {      
        EntityPlayer p = e.getEntityPlayer();
        Location l = new Location(p);
        l.round();
        parser.callEvent("player_move", (sc) -> ScriptVars.setPlayerVars(sc, p), null, 
                sc -> ((MinecraftScript) sc).removeLocation(l));
      
        PlayerScript data = KajetansMod.scripts.getScript(p);
        if(data != null)
        {
            parser.callEvent("player_move", data, (sc) -> 
            {
                ScriptVars.setPlayerVars(sc, p); 
            }, null, data.removeLocation(l));
        }
    } 
    
    public boolean onInventoryClick(Script qd, SnuviInventory inv, int slot, ClickType click, EntityPlayer p)
    {
        parser.callEvent("inv_click", qd, sc -> 
        {
            ScriptVars.setPlayerVars(qd, p); 
            qd.setEventVar("inv_id", new Fraction(inv.getId()));
            qd.setEventVar("inv_name", inv.getName());
            qd.setEventVar("inv_slot", new Fraction(slot));
            ScriptVars.setItemVars(qd, inv.getStackInSlot(slot));
            qd.setVar("cancel", false); 
        }, null);
        return qd.getBooleanVar("cancel");
    }

    public void onInventoryClose(Script qd, SnuviInventory inv, EntityPlayer p)
    {
        parser.callEvent("inv_close", qd, sc -> 
        {
            ScriptVars.setPlayerVars(qd, p); 
            qd.setEventVar("inv_id", new Fraction(inv.getId()));
            qd.setEventVar("inv_name", inv.getName());
        }, null);
    }
    
    public void onHumanHurt(EntityPlayer p, EntityHuman h)
    {
        handleEvent(p, "human_hurt", sc -> ScriptVars.setEntityVars(sc, h));
    }
    
    @SubscribeEvent
    public void onPlayerRespawn(PlayerRespawnAtEvent e)
    {
        handleEvent(e.getEntityPlayer(), "player_respawn", (qd) -> 
        {
            qd.setEventVar("keep_inventory", e.isInventoryKeepingForced());
            qd.setEventVar("respawn_loc", new Location(e.getWorld(), e.getRespawnLoc()));
        }, (qd) -> 
        {
            try 
            {
                e.setForcedInventoryKeeping(qd.getBooleanVar("keep_inventory"));
                Location l = (Location) qd.getVar("respawn_loc");
                e.setRespawnLoc(l.getPos());
                e.setWorld((WorldServer) l.getWorld());
            } 
            catch(ClassCastException | NullPointerException ex) 
            {
                KajetansMod.scripts.logger.printException(ex, "player_respawn", qd, qd.getActiveRealCodeLine());
            }
        });
    }
    
    @SubscribeEvent
    public void onPlayerDamage(PlayerHurtEvent e)
    {        
        EntityPlayer p = e.getEntityPlayer();  
        handleEvent(p, "player_hurt", (qd) -> 
        {
            if(p.getHealth() <= e.getAmount())
            {
                qd.setEventVar("player_killed", true);   
            }  
            else
            {
                qd.setEventVar("player_killed", false); 
            }
            qd.setEventVar("player_damage", Fraction.fromDouble(e.getAmount()));   
            qd.setEventVar("player_damage_cause", e.getSource().getDamageType());
            EntityPlayer ent = Utils.getDamager(e.getSource());
            if(ent != null)
            {
                qd.setEventVar("player_involved", true);
                ScriptVars.setSecPlayer(qd, ent);
            }
            else
            {
                qd.setEventVar("player_involved", false);
            }
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            try
            {
                e.setAmount(ScriptUtils.getFloat(qd.getVar("player_damage")));
            }
            catch(Exception ex)
            {
                KajetansMod.scripts.logger.printException(ex, "player_hurt", qd, qd.getActiveRealCodeLine());
            }
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    } 
    
    @SubscribeEvent
    public void onPlayerDamage(LivingHealEvent e)
    {        
        if(e.getEntityLiving() instanceof EntityPlayer)
        {
            handleEvent((EntityPlayer) e.getEntityLiving(), "player_heal", (qd) -> 
            {
                qd.setEventVar("heal", Fraction.fromDouble(e.getAmount()));
                qd.setVar("cancel", e.isCanceled()); 
            }, (qd) -> 
            {
                try
                {
                    e.setAmount(ScriptUtils.getFloat(qd.getVar("heal")));
                }
                catch(Exception ex)
                {
                    KajetansMod.scripts.logger.printException(ex, "player_heal", qd, qd.getActiveRealCodeLine());
                }
                e.setCanceled(qd.getBooleanVar("cancel")); 
            });
        }
    } 
    
    @SubscribeEvent
    public void onLivingDeath(LivingDeathEvent e)
    {        
        if(!(e.getEntityLiving() instanceof EntityPlayer))
        {
            handleEvent(null, "entity_death", (qd) -> 
            {
                EntityLivingBase ent = e.getEntityLiving();
                ScriptVars.setEntityVars(qd, ent);
                EntityPlayer p = Utils.getDamager(e.getSource());
                if(p != null)
                {
                    qd.setEventVar("player_involved", true);
                    ScriptVars.setPlayerVars(qd, p);
                }  
                else
                {
                    qd.setEventVar("player_involved", false);
                }
                qd.setVar("cancel", e.isCanceled()); 
            }, (qd) -> 
            {
                e.setCanceled(qd.getBooleanVar("cancel")); 
            });
            return;
        }
        EntityPlayer p = (EntityPlayer) e.getEntity();    
        handleEvent(p, "player_death", (qd) -> 
        {
            qd.setEventVar("clear", false);
            EntityPlayer ent = Utils.getDamager(e.getSource());
            if(ent != null)
            {
                ScriptVars.setSecPlayer(qd, ent);
            }
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            if(qd.getBooleanVar("clear"))
            {
                p.inventory.clear();
            } 
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    } 
       
    @SubscribeEvent
    public void onEntityDamage(LivingHurtEvent e)
    {        
        EntityPlayer p = Utils.getDamager(e.getSource());
        if(p == null)
        {
            return;
        }
        handleEvent(p, "entity_hurt", (qd) -> 
        {
            qd.setEventVar("entity_killed", e.getEntityLiving().getHealth() <= e.getAmount());        
            ScriptVars.setEntityVars(qd, e.getEntity()); 
            qd.setEventVar("entity_damage", Fraction.fromDouble(e.getAmount()));   
            qd.setEventVar("entity_damage_cause", e.getSource().getDamageType());
            qd.setVar("cancel", e.isCanceled());   
        }, (qd) -> 
        {
            try
            {
                e.setAmount(ScriptUtils.getFloat(qd.getVar("entity_damage")));
            }
            catch(Exception ex)
            {
                KajetansMod.scripts.logger.printException(ex, "entity_hurt", qd, qd.getActiveRealCodeLine());
            }
            e.setCanceled(qd.getBooleanVar("cancel"));
        });
    }
    
    @SubscribeEvent
    public void onEntityDrop(LivingDropsEvent e)
    {             
        try
        {
            handleEvent(null, "entity_drop", (qd) -> 
            {
                qd.setEventVar("drops", e.getDrops());
                ScriptVars.setEntityVars(qd, e.getEntityLiving());
                EntityPlayer p = Utils.getDamager(e.getSource());
                if(p != null)
                {
                    qd.setEventVar("player_involved", true);
                    ScriptVars.setPlayerVars(qd, p);
                }  
                else
                {
                    qd.setEventVar("player_involved", false);
                }
                qd.setVar("cancel", e.isCanceled()); 
            }, (qd) -> 
            {
                e.setCanceled(qd.getBooleanVar("cancel"));
            });
        }
        catch(NullPointerException ex)
        {
            KajetansMod.scripts.sendWarningToConsole(ex.toString() + "  " + ex.getMessage());
        }
    } 
            
    @SubscribeEvent
    public void onPlayerDrop(PlayerDropsEvent e)
    {             
        handleEvent(e.getEntityPlayer(), "player_drop", (qd) -> 
        {
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel"));
        });
    } 
    
    @SubscribeEvent
    public void onProjectileHit(ThrowableImpactEvent e)
    {        
        EntityPlayer p = Utils.getPlayerFromProjectile(e.getEntityThrowable());
        if(p != null)
        {
            handleEvent(p, "throw_hit", (qd) -> 
            {
                if(e.getRayTraceResult().entityHit != null)
                {
                    qd.setEventVar("is_entity_hit", true);
                    qd.setEventVar("entity_hit", e.getRayTraceResult().entityHit);
                }
                else
                {
                    qd.setEventVar("is_entity_hit", false);
                }
                ScriptVars.setEntityVars(qd, e.getEntityThrowable());
            });
        }           
    }
    
    public void onEntityItemProjectileHit(EntityItemProjectile ent, EntityPlayer p, ItemStack stack, List<Entity> ents)
    {        
        handleEvent(p, "item_hit", (qd) -> 
        {
            ScriptVars.setEntityVars(qd, ent);
            ScriptVars.setItemVars(qd, stack);
            qd.setEventVar("entities", ents);
        });      
    }
    
    @SubscribeEvent
    public void onEntityShear(PlayerInteractEvent.EntityInteract e)
    {              
        if(e.getHand() == EnumHand.OFF_HAND || !(e.getTarget() instanceof EntitySheep))
        {
            return;
        }
        EntityPlayer p = e.getEntityPlayer();   
        if(p.getHeldItemMainhand().getItem() != Items.SHEARS)
        {
            return;
        }
        EntitySheep sheep = (EntitySheep) e.getTarget();
        handleEvent(p, "entity_shear", (qd) -> 
        {
            ScriptVars.setEntityVars(qd, sheep);
            qd.setEventVar("entity_sheared", sheep.getSheared());
            qd.setEventVar("entity_color", sheep.getFleeceColor().toString());
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel")); 
            sheep.setSheared(qd.getBooleanVar("entity_sheared"));
        });
    }
       
    @SubscribeEvent
    public void onBlockBreak(BlockEvent.BreakEvent e)
    {        
        handleEvent(e.getPlayer(), "block_break", (qd) -> 
        {
            ScriptVars.setBlockVars(qd, e.getWorld(), e.getPos());
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    }
    
    @SubscribeEvent
    public void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent e)
    {    
        EntityPlayer p = e.player;
        if(p == null)
        {
            return;
        }
        handleEvent(p, "player_login", (qd) -> 
        {       
            qd.setEventVar("first_join", Utils.hasPlayedBefore(p));
            PlayerList list = KajetansMod.server.getPlayerList();
            qd.setEventVar("is_banned", list.getBannedPlayers().isBanned(p.getGameProfile()));
            qd.setEventVar("is_whitelisted", list.getWhitelistedPlayers().isWhitelisted(p.getGameProfile()));
        });
    }
    
    @SubscribeEvent
    public void onPlayerJoin(PlayerJoinMessageEvent e)
    {    
        EntityPlayer p = e.getEntityPlayer();
        handleEvent(p, "player_join_server", (qd) -> 
        {       
            qd.setEventVar("message", e.getMessage());
            qd.setEventVar("changed_name", e.hasNameChanged());
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setMessage(String.valueOf(qd.getVar("message"))); 
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    }
    
    @SubscribeEvent
    public void onPlayerLeave(PlayerLeaveMessageEvent e)
    {      
        EntityPlayer p = e.getEntityPlayer();
        handleEvent(p, "player_leave", (qd) -> 
        {       
            qd.setEventVar("message", e.getMessage());
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setMessage(String.valueOf(qd.getVar("message"))); 
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    }
    
    @SubscribeEvent
    public void onBucketFill(FillBucketEvent e)
    {      
        handleEvent(e.getEntityPlayer(), "bucket_fill", (qd) -> 
        {
            RayTraceResult ray = e.getTarget();
            if(ray != null)
            {
                BlockPos pos = ray.getBlockPos();
                if(pos != null)
                {
                    qd.setEventVar("has_block", true); 
                    ScriptVars.setBlockVars(qd, e.getWorld(), pos);
                }
                else
                {
                    qd.setEventVar("has_block", false); 
                }
            }
            else
            {
                qd.setEventVar("has_block", false); 
            }
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    } 
       
    @SubscribeEvent
    public void onBlockPlace(BlockEvent.PlaceEvent e)
    {
        handleEvent(e.getPlayer(), "block_place", (qd) -> 
        {
            qd.setEventVar("block_type_after", e.getPlacedBlock().getBlock().getRegistryName());
            ScriptVars.setBlockVars(qd, e.getWorld(), e.getPos());
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    }    
    
    @SubscribeEvent
    public void onRightClickBlock(PlayerInteractEvent.RightClickBlock e)
    {
        if(e.getHand() == EnumHand.OFF_HAND)
        {
            return;
        }
        handleEvent(e.getEntityPlayer(), "block_click", (qd) -> 
        {
            qd.setEventVar("action", "right");
            ScriptVars.setBlockVars(qd, e.getWorld(), e.getPos());
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });       
    }
    
    @SubscribeEvent
    public void onLeftClickBlock(PlayerInteractEvent.LeftClickBlock e)
    {
        if(e.getHand() == EnumHand.OFF_HAND)
        {
            return;
        }
        handleEvent(e.getEntityPlayer(), "block_click", (qd) -> 
        {
            qd.setEventVar("action", "left");
            ScriptVars.setBlockVars(qd, e.getWorld(), e.getPos());
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });       
    }
    
    @SubscribeEvent(receiveCanceled = true)
    public void onEntityClick(PlayerInteractEvent.EntityInteract e)
    {
        if(e.getHand() != EnumHand.OFF_HAND)
        {
            handleEvent(e.getEntityPlayer(), "entity_click", (qd) -> 
            {
                ScriptVars.setEntityVars(qd, e.getTarget());
                qd.setVar("cancel", e.isCanceled()); 
            }, (qd) -> 
            {
                e.setCanceled(qd.getBooleanVar("cancel")); 
            });
        }
    }
    
    @SubscribeEvent
    public void onFishing(ItemFishedEvent e)
    {
        handleEvent(e.getEntityPlayer(), "fishing", (qd) -> 
        {
            qd.setEventVar("drops", e.getDrops());
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    }
    
    @SubscribeEvent
    public void onItemClick(PlayerInteractEvent.RightClickItem e)
    {        
        handleEvent(e.getEntityPlayer(), "item_air_click", (qd) -> 
        {
            ScriptVars.setItemVars(qd, e.getItemStack());
            qd.setEventVar("hand", e.getHand().toString());
            qd.setVar("cancel", e.isCanceled());  
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel"));
        });
    }
    
    public void onArmSwing(EntityPlayer p, EnumHand hand)
    {        
        handleEvent(p, "arm_swing", (qd) -> 
        {
            qd.setEventVar("hand", hand);
        });
    }
    
    @SubscribeEvent
    public void onItemUseStart(LivingEntityUseItemEvent.Start e)
    {        
        if(!(e.getEntityLiving() instanceof EntityPlayer))
        {
            return;
        }
        handleEvent((EntityPlayer) e.getEntityLiving(), "item_use_start", (qd) -> 
        {
            qd.setEventVar("duration", new Fraction(e.getDuration()));
            qd.setVar("cancel", e.isCanceled());  
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel"));
            try
            {
                e.setDuration(ScriptUtils.getInt(qd.getVar("duration")));
            }
            catch(Exception ex)
            {
                KajetansMod.scripts.logger.printException(ex, "item_use_start", qd, qd.getActiveRealCodeLine());
            }
        });
    }
    
    @SubscribeEvent
    public void onConsuming(LivingEntityUseItemEvent.Finish e)
    {
        if(!(e.getEntityLiving() instanceof EntityPlayer))
        {
            return;
        }
        handleEvent((EntityPlayer) e.getEntityLiving(), "item_use_finish", (qd) -> 
        {
            qd.setEventVar("result_stack", e.getResultStack());  
        });
    }
    
    @SubscribeEvent
    public void onCrafting(PlayerEvent.ItemCraftedEvent e)
    {  
        handleEvent(e.player, "craft", (qd) -> 
        {
            ScriptVars.setItemVars(qd, e.crafting);
        });   
    } 
    
    @SubscribeEvent
    public void onItemDrop(ItemTossEvent e)
    {
        handleEvent(e.getPlayer(), "player_toss", (qd) -> 
        {
            ScriptVars.setItemVars(qd, e.getEntityItem().getItem());   
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    }
    
    @SubscribeEvent
    public void onItemPickup(EntityItemPickupEvent e)
    {
        handleEvent(e.getEntityPlayer(), "player_pickup", (qd) -> 
        {
            ScriptVars.setEntityVars(qd, e.getItem()); 
            ScriptVars.setItemVars(qd, e.getItem().getItem());  
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    }
    
    @SubscribeEvent
    public void onVehicleEnter(EntityMountEvent e)
    {
        if(!(e.getEntityMounting() instanceof EntityPlayer))
        {
            return;
        }
        EntityPlayer p = (EntityPlayer) e.getEntityMounting();
        handleEvent(p, "entity_mount", (qd) -> 
        {
            qd.setEventVar("mounting", e.isMounting()); 
            ScriptVars.setEntityVars(qd, e.getEntityBeingMounted()); 
            qd.setVar("cancel", e.isCanceled()); 
        }, (qd) -> 
        {
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    }
    
    @SubscribeEvent
    public void onPlayerUsePortal(PlayerEvent.PlayerChangedDimensionEvent e)
    {
        handleEvent(e.player, "portal", (qd) -> 
        {
            qd.setEventVar("from", ModDimensions.getWorldName(e.fromDim));
            qd.setEventVar("to", ModDimensions.getWorldName(e.toDim));
        });
    }
    
    @SubscribeEvent
    public void onCommand(CommandEvent e)
    {
        if(e.getSender() instanceof EntityPlayer)
        {
            handleEvent((EntityPlayer) e.getSender(), "command", (qd) -> 
            {
                qd.setEventVar("command", e.getCommand().getName()); 
                qd.setEventVar("args", Arrays.stream(e.getParameters()).map(s -> Code.convertInput(null, s, false)).collect(Collectors.toList()));
                qd.setVar("cancel", e.isCanceled()); 
            }, (qd) -> 
            {
                e.setCanceled(qd.getBooleanVar("cancel")); 
            });
        }
    }
    
    public void onCustomCommand(EntityPlayer p, String command, String args)
    {
        handleEvent(p, "custom_command", (qd) -> 
        {
            qd.setEventVar("command", command);
            if(args.length() == 0)
            {
                qd.setEventVar("args", new ArrayList<>());
                return;
            }
            qd.setEventVar("args", Arrays.stream(args.trim().split(" ")).map(s -> Code.convertInput(null, s, false)).collect(Collectors.toList()));
        });
    }
      
    public void onEffectUse(PlayerUsesEffectEvent e)
    {    
        handleEvent(e.getPlayer(), "player_use_effect", (qd) -> 
        {       
            qd.setEventVar("power", new Fraction(e.getPower()));
            qd.setEventVar("mana_cost", new Fraction(e.getMana()));
            qd.setEventVar("cause", e.getCause().toString());
            qd.setEventVar("effect", e.getEffect());
            qd.setVar("cancel", e.isCanceled());
        }, (qd) -> 
        {
            try
            {
                int power = ScriptUtils.getInt(qd.getVar("power"));
                if(power < 1 || power > 20)
                {
                    throw new IllegalArgumentException();
                }
                e.setPower(power);
            }
            catch(Exception ex)
            {
                KajetansMod.scripts.logger.printException(ex, "player_use_effect", qd, qd.getActiveRealCodeLine());
            }
            try
            {
                int mana = ScriptUtils.getInt(qd.getVar("mana_cost"));
                if(mana < 0)
                {
                    throw new IllegalArgumentException();
                }
                e.setMana(mana);
            }
            catch(Exception ex)
            {  
                KajetansMod.scripts.logger.printException(ex, "player_use_effect", qd, qd.getActiveRealCodeLine());
            }
            e.setCanceled(qd.getBooleanVar("cancel")); 
        });
    }
    
    public void onFunctionKey(EntityPlayerMP p, int key)
    {        
        handleEvent(p, "function_key", (qd) -> 
        {
            qd.setEventVar("key", new Fraction(key));
        });      
    }
    
    /*@SubscribeEvent
    public void QuestVillagerPickUpItem(ItemTossEvent e)
    {             
        EntityPlayer p = e.getPlayer();  
        PlayerScript qd = KajetansMod.scripts.getScript(p);
        if(qd == null || !qd.isLoadedEvent("villager_give"))
        {
            return;
        }
        final EntityItem itemEnt = e.getEntityItem();
        KajetansMod.scheduler.scheduleTask(() -> 
        {
            if(itemEnt.isDead)
            {
                return;
            }
            EntityVillager v = Utils.getNearestEntity(p.world, itemEnt.getPositionVector(), 2, EntityVillager.class);
            if(v == null)
            {
                return;
            }
            Script data = KajetansMod.scripts.getScript(p);
            if(data == null || !data.isLoadedEvent("villager_give"))
            {
                return;
            }
            data.setVar("event", "villager_give");
            ScriptVars.setPlayerVars(data, p); 
            ScriptVars.setItemVars(data, itemEnt.getItem());
            data.setEventVar("villager_loc", new Location(v));                    
            data.setEventVar("villager_prof", v.getProfessionForge().getRegistryName());
            data.setVar("cancel", e.isCanceled()); 
            data.runCode();
            if(data.getBooleanVar("cancel"))
            {
                return;
            }                  
            itemEnt.setDead();                
        }, 40);                    
    }*/
}