Merge branch 'dev' into syntax-highlighter-unified

This commit is contained in:
Kam committed 2026-03-11 14:08:19 -04:00
commit f18ae4e08f
66 files changed
+623 -559

No files matched your search

+402 -65
View File
@@ -1,95 +1,432 @@
![CustomNPC+ Banner](images/logo_banner.png)
## 👋 Welcome to the CustomNPC+ Repository.
![](images/logo_banner.png)
# CustomNPC+
> CustomNPC+ is a [Minecraft](https://minecraft.net/) mod that allows you to add custom NPCs to your world. It is developed for creative and storytelling players who want to make their Minecraft worlds more in-depth and unique. CustomNPC+ is only a branch version of the **1.7.10** Forge version of the original (down below) and has no plans to add or update any other version. This is not an official version of CustomNPC.
**CustomNPC+** is a Minecraft 1.7.10 Forge mod that lets you create fully customizable NPCs with advanced AI, combat abilities, quests, dialogs, scripting, an economy system, and much more. Built for creative and storytelling players who want to transform their worlds into living RPG experiences.
----------------
> CustomNPC+ is a branch version of the original [CustomNPC](https://www.curseforge.com/minecraft/mc-mods/custom-npcs) by [Noppes](https://github.com/Noppes), developed with permission. This is not an official version of CustomNPC.
<a href="https://discord.gg/pQqRTvFeJ5"> <img src="https://img.shields.io/badge/KAMKEEL_Discord-7289DA?style=for-the-badge&logo=discord&logoColor=white" width="400" height="60"> </a>
<a href="https://ko-fi.com/kamkeel"> <img src="https://img.shields.io/badge/Support_Me_|_Ko--fi-F16061?style=for-the-badge&logo=ko-fi&logoColor=white" alt="Support Me" width="400" height="60"> </a>
---
<a href="https://discord.gg/pQqRTvFeJ5"> <img src="images/Discord.png" width="400" height="60"> </a>
[![Download CustomNPC+](https://img.shields.io/badge/CustomNPC+-0081CB?style=for-the-badge&logo=material-ui&logoColor=white)](https://modrinth.com/mod/customnpc-plus)
[![Download MPM+](https://img.shields.io/badge/MorePlayerModels+-0081CB?style=for-the-badge&logo=material-ui&logoColor=white)](https://www.curseforge.com/minecraft/mc-mods/moreplayermodels-plus)
[![Download PluginMod](https://img.shields.io/badge/Plugin_Mod-0081CB?style=for-the-badge&logo=material-ui&logoColor=white)](https://github.com/KAMKEEL/Plugin-Mod)
----------------
---
### ❗ Notice
I am not the original creator of CustomNPC. The original creator is @[Noppes](https://github.com/Noppes). I have been permitted to update and develop my own branch version for 1.7.10. The original mod that is updated to the latest versions of Minecraft can be found on these links: [CurseForge](https://www.curseforge.com/minecraft/mc-mods/custom-npcs), [kodevelopment](http://www.kodevelopment.nl/minecraft/customnpcs)
## Installation
### ⬇️ Downloads
CustomNPC+ is a **replacement** for the original CustomNPC — not an add-on. Do not install both together. Simply drop CustomNPC+ into your client or server mods folder. All original CustomNPC features are preserved and expanded upon.
> Please **backup your world** before installing. Report any bugs on the [issue tracker](https://github.com/KAMKEEL/CustomNPC-Plus/issues).
### Downloads
- **Modrinth**: [Download](https://modrinth.com/mod/customnpc-plus)
- **CurseForge**: [Download](https://www.curseforge.com/minecraft/mc-mods/customnpc-plus)
### 🔹 Installation
This mod is an ***update*** to CustomNPC and not an add-on. Do not install CustomNPC with CustomNPC-Plus. CustomNPC-Plus does not remove any functional features from the original mod. It expands upon CustomNPC with original features and backported features from newer versions. Simply drag CustomNPC-Plus into any client/server mods folder to utilize. Please **backup** before using as this could still have unforeseen errors. Report any bugs if found, thank you and enjoy.
---
### ❓ Features:
- Flying NPC (Smart Pathfinding)
- 1.8 Skin Support (64x64) (Alex/Steve64)
- Full URL64 Selector (Downloads FULL Size Images for Custom Mob Skins)
- Additional Parts: [Wings, Fins, Capes, Horns, Tail]
- Hide Body Parts [Arms, Legs, Head, Body]
- Custom Overlays
- Animation Maker
- Enhanced Cloner GUI (More Tabs, Filters, Tags)
- Markov Name Generator
- Dark Mode GUI (Link Below)
- 1.12 Scripting Features (Details below)
- Profile System (Multiple Character Slots)
- Quest Party System
- Item and Armor Attributes
- Magic System with Elemental Cycles
- Custom Effect Engine
- Scripted Blocks & Particles
- Compass Marks for Waypoints
- Custom Model Support
- Gecko & Dragon Block C Addons
<a href="https://ko-fi.com/kamkeel"> <img src="images/Kofi.png" width="400" height="60"> </a>
#### Scripting:
- CustomNPC+ Api: [API](https://github.com/KAMKEEL/CustomNPC-Plus-API)
- Java Doc: [kamkeel.github.io/CustomNPC-Plus](https://kamkeel.github.io/CustomNPC-Plus/)
- Scripted Items
- Player Overlays
- Script Timers
- Global Scripts
- Script Logging
- GUI Overlays
- Event Scripting
(We never remove script functionality, only add on existing functions)
> More betas with new features will continue to be released to supporters for **Early Access** to new content!
## 🔰 Skin Model Feature:
###### **Majority of these cape designs are taken from the internet with fair-usage.*
##### ❔ Please note all *local* skins in this version of CustomNPC, must match the model selected.
<img src="images/skinAnimation.gif" width="474" height="260"/>
---
## Feature Overview
### NPC Creation & Customization
Create NPCs with deep visual customization and lifelike behavior.
<img src="images/NPCEditor.gif" width="474" height="260"/>
- **Skin System** - 1.8 skin support (64x64), Alex & Steve64 models, full URL64 selector that downloads full-size images for custom skins
- **Body Parts** - Wings, fins, capes, horns, tails. Hide individual body parts (arms, legs, head, body)
- **Skin Overlays** - Layer additional textures on top of the base skin with color tinting and alpha control
- **Custom Models** - Support for custom model formats, plus Gecko and DBC addon model integration
- **Hitbox Scaling** - Independently scale NPC hitbox width and height
- **Tint System** - Control hurt flash color and apply persistent color tints
---
### NPC AI & Behavior
NPCs have a full AI system for combat, pathing, and interaction.
- **Flying NPCs** - Smart pathfinding in 3D space for flying entities
- **Combat AI** - Multiple tactical behaviors: attack, ranged attack, ambush, pounce, dodge-shoot, zigzag, stalk, orbit, sprint-to-target, and leap attacks
- **Movement Types** - Standing, wandering, and moving path modes
- **Pathfinding** - Ground, flying, and water navigation
- **Door Interaction** - NPCs can break, open, or ignore doors
- **Behavior Controls** - Configurable sprinting, swimming, fire reactions, tactical chance, and walking range
- **Attack Speed** - NPCs respect attack speed timing when dealing damage
- **Faction Awareness** - NPCs respond to faction standings. Passive faction NPCs are protected from damage
#### Flying NPCs:
<img src="images/FlyingNPC.gif" width="474" height="260"/>
#### Url64:
<img src="images/Url64Example.gif" width="474" height="260"/>
---
#### DarkMode + Cloner GUI:
<img src="images/ClonerGUI-DarkMode.png" width="474" height="260"/>
### NPC Roles & Jobs
## 🔗 Want to check out my other projects?
[Plugin Mod](https://github.com/KAMKEEL/Plugin-Mod) is a mod designed to add more aesthetic items for RPG and storytelling. It is compatible with CustomNPC+ and has many items intended to be used and wielded by created NPCs.
**Roles** give NPCs interactive functions — Trader, Follower, Bank, Transporter, Postman, Companion, Mount, and Auctioneer. **Jobs** give NPCs background tasks — Bard, Healer, Guard, Item Giver, Follower, Spawner, Conversation, and Chunk Loader.
[RPG Messenger](https://github.com/KAMKEEL/RPGMessenger) is a bukkit plugin designed to allow operators to create messagable NPCs for RPG Servers. Allows a 'RPG Controller' to reply quickly as NPCs, form groupchats, and messagable parties.
---
[CustomNPC+ Dark Mode](https://github.com/KAMKEEL/CustomNPC-Plus-Dark-Mode) is a resource pack for CustomNPC+ that alters all GUIs within the mod to a dark variant that is both clean and pleasing to look at.
### Dialog System
## Cloning / Compiling / Building
Create branching dialog trees for storytelling and player interaction.
git clone call `git submodule update --init --recursive`
<img src="images/Dialog.png" width="474" height="260"/>
Using IntelliJ, take the following steps:
1. Run `setupDecompWorkspace` under the `forgegradle` tab.
2. Refresh Gradle.
3. Run `genIntellijRuns` under the `other` tab to set up your Minecraft runs.
- **Dialog Trees** - Multi-level branching conversations with unlimited depth
- **Dialog Options** - Player choices that affect outcomes, trigger quests, or change faction standings
- **Dialog Images** - Display images alongside dialog text
- **Dialog Colors** - Customizable text and background colors
- **Categories** - Organize dialogs into categories for large projects
- **Availability Conditions** - Show or hide dialogs based on quests, factions, items, or custom conditions
- **Script Hooks** - Full scripting support for dialog open, close, and option events
or for Eclipse, run `gradlew setupDecompWorkspace eclipse` then import the project.
---
Mixin code will not work if you do not add `--tweakClass org.spongepowered.asm.launch.MixinTweaker --mixin customnpcs.mixins.json` to your program arguments.
### Quest System
A complete quest framework for RPG progression.
<img src="images/QuestMarks.png" width="474" height="260"/>
- **Quest Types** - Item collection, dialog completion, kill targets, reach locations, area kills, and fully manual (script-driven) quests
- **Quest Tracking** - On-screen HUD overlay showing active quest objectives
- **Categories** - Organize quests into categories
- **Rewards** - Configure item, XP, and currency rewards
- **Cooldowns** - Repeatable quests with MC time, real-time, or custom cooldown intervals
- **Party Quests** - Share quest objectives across party members with configurable sharing modes
- **Availability** - Control quest visibility based on completed quests, faction standing, dialogs, and more
- **Script Hooks** - Events for quest start, completion, turn-in, and custom quest objectives
---
### Ability System
A full combat ability system for both NPCs and players.
<img src="images/NPCAbilities.gif" width="474" height="260"/>
<img src="images/PlayerAbilities.gif" width="474" height="260"/>
**Melee Abilities** - Slam, Heavy Hit, Cutter
**Energy Projectiles** - Orbs, Discs, Beams, Laser Shots with full visual customization, homing, and hit types
**Energy Barriers** - Domes, Walls, Shields with HP, reflection, and damage absorption
**Defensive** - Guard, Counter, Dodge with configurable damage reduction and counter-attacks
**Movement** - Charge, Dash, Teleport
**AOE** - Shockwave, Sweeper, Vortex
**Hazards & Traps** - Persistent damage zones and proximity-triggered traps with visual presets
**Support** - Ability Effect for healing, buffs, and debuffs
**Key Features:**
- Phase-based execution (Wind-up, Active, Dazed) with visual telegraphs
- Burst system for rapid multi-fire
- Chained abilities for sequential combos
- Player ability hotbar (12 slots) with unlock system
- Full scripting API for every lifecycle event
- Live preview in the ability editor
---
### Auction House & Economy
A complete marketplace and economy system.
<img src="images/Auction.png" width="474" height="260"/>
- **Built-in Currency** - Configurable currency with starting balance, max cap, and Vault plugin integration
- **Auction House** - Global player marketplace with bidding, buyout, snipe protection, and notifications
- **Trade Slots** - Configurable per-player slot limits via permissions
- **Item Blacklist** - Block items by name, wildcard, mod, or NBT tag from being listed
- **Admin Tools** - Dedicated admin GUI for managing listings and creating server auctions
- **Scripting API** - Cancelable events for creates, bids, buyouts, cancels, and claims
**Trader Enhancements:**
- Limited stock with automatic restocking (MC time, real-time, or custom intervals)
- Global or per-player stock modes
- Currency costs on top of item trades
- Client-side balance preview
---
### Script Editor
A full-featured code editor built right into Minecraft.
<img src="images/ScriptEditor.gif" width="474" height="260"/>
- **Syntax Highlighting** - Color-coded keywords, types, variables, methods, strings, and comments
- **Autocomplete** - Smart suggestions with type awareness, static vs instance context, and auto-imports
- **Error Detection** - Real-time underlining of wrong argument types, missing methods, type mismatches, and unused imports
- **Hover Info** - Full type information, documentation, and JSDoc rendering on hover
- **Navigation** - Go to line, search & replace, go to definition, and fullscreen mode
- **Editing** - Smart undo/redo, duplicate lines, comment toggle, rename refactoring, smart bracket pairing
- **Visual** - Line numbers, smooth scrolling, indent guides, brace matching, resizable panels
---
### Scripting
Write scripts in **JavaScript** or **Java** to control every aspect of the mod.
- **Java Scripting** - Write real Java code compiled by the Janino engine. Full support for classes, generics, lambdas, and method references
- **JavaScript** - Full Nashorn-based JavaScript with ECMAScript 5.1 globals (Math, JSON, Date, Number)
- **Client-Side Scripting** - Run scripts on the client for responsive visual experiences (server-controlled)
- **154+ Script Hooks** - Events for NPCs, players, blocks, items, quests, dialogs, factions, abilities, auction, animations, and more
- **Script Types** - NPC, Player, Global, Block, Item, Linked Item, Effect, Ability, Recipe, and Forge Event scripts
- **Scripted Blocks** - Blocks with full script hooks for click, redstone, neighbor changes, and more
- **Scripted Items** - Items with use, attack, toss, pickup, and durability hooks
- **Custom GUIs** - Build custom interfaces with buttons, text fields, scroll panels, and slots via scripting
- **GUI Overlays** - Persistent on-screen overlays with labels, lines, and textured rectangles
- **Addon API** - Mods can ship `.d.ts` type definitions for full autocomplete in the editor
**Resources:**
- API Repository: [CustomNPC+ API](https://github.com/KAMKEEL/CustomNPC-Plus-API)
- Java Docs: [kamkeel.github.io/CustomNPC-Plus](https://kamkeel.github.io/CustomNPC-Plus/)
<img src="images/ScriptedBlock.png" width="474" height="260"/>
<img src="images/ScriptedParticle.png" width="474" height="260"/>
---
### Animation System
Create custom animations for NPCs directly in-game.
- **Animation Maker** - Visual editor for creating frame-based animations
- **Animation Parts** - Animate individual body parts with rotation, position, and scaling
- **Frame Events** - Script hooks for animation start, end, frame enter, and frame exit
- **Data Store** - Animations can store and pass data across events and frames
- **Task System** - Consumer-based callbacks for animation lifecycle
- **Built-in Animations** - Pre-made animations for abilities that work automatically
- **Multiple Registrations** - Register multiple animations under one common name
- **Animated Textures** - Frame-based animated textures for items, effects, and ability icons
<img src="images/AnimationMaker.gif" width="474" height="260"/>
---
### Custom Effects
Create custom status effects beyond vanilla potions.
- **Custom Icons** - Each effect gets its own texture, with support for animated icons
- **Script Hooks** - Events for effect apply, tick, and removal
- **Ability Integration** - Abilities can apply custom effects to targets
- **Condition System** - Check if entities have specific effects for ability or availability conditions
---
### Profile System
Multiple character slots on a single account.
- **Profile Slots** - Each slot stores separate quest progress, dialogs, factions, currency, abilities, and effects
- **Region Locking** - Lock profiles to specific areas
- **Soulbound Items** - Bind items to a specific player by UUID
- **Profile-Slotbound Items** - Bind items to a specific profile slot
---
### Party System
Group up with other players for cooperative gameplay.
- **Party Management** - Invite, kick, leave, disband, and transfer leadership
- **Quest Sharing** - Share quest objectives across party members
- **Ability Integration** - Party members are treated as allies for AOE ability targeting
- **Script Hooks** - Events for invites, kicks, leaves, disbands, and quest completion
<img src="images/PartySystem.png" width="474" height="260"/>
<img src="images/QuestParty.png" width="474" height="260"/>
---
### Faction System
Create factions to control NPC relationships and player reputation.
- **Faction Points** - Players earn or lose standing with factions through quests, combat, and scripting
- **Availability Conditions** - Gate dialogs, quests, and content behind faction thresholds
- **NPC Behavior** - NPCs react to players based on faction standing (friendly, neutral, hostile)
- **Passive Factions** - Passive faction NPCs are protected from damage
---
### HUD System
Customizable on-screen elements.
- **HUD Editor** - Drag and position HUD components to fit your screen
- **Ability Hotbar** - 12-slot bar with horizontal/vertical layout and configurable visible slots
- **Quest Tracking** - On-screen quest objective tracker
- **Compass** - Directional compass with quest and waypoint markers
- **Effect Bar** - Display active custom effects
<img src="images/HudSystem.png" width="474" height="260"/>
---
### Cloner & Organization
Save, organize, and reuse NPCs and content.
- **Cloner Tabs** - Save and load NPCs, items, and other content
- **Folder System** - Organize with folders and subfolders for animations, abilities, forms, auras, outlines, effects, and linked items
- **Quick Tags** - Tag-based filtering for fast searching
- **Full Screen Browser** - Wide directory view for large collections
<img src="images/ClonerTabs.png" width="474" height="260"/>
---
### Items & Blocks
A large library of custom items and decorative blocks.
**Weapons:** Battle Axe, Broadsword, Claw, Crossbow, Dagger, Staff, Glaive, Gun, Kunai, Scythe, Spear, Wand, Warhammer, Musket, and more
**Linked Items:** Create custom items with configurable attack speed, scripted behavior, animated textures, and item versioning
**Blocks:** Banners, barrels, beams, signs, campfires, candles, chairs, couches, crates, crystals, lanterns, mailboxes, pedestals, redstone blocks, and scripted blocks
**Crafting:** Carpentry Bench and Anvil with custom recipe support
<img src="images/VariousWeapons.png" width="474" height="260"/>
<img src="images/JappaTextures.png" width="474" height="260"/>
---
### Custom Models
Support for custom NPC models beyond the default humanoid. Import Blockbench models using the **Gecko Addon** (GeckoLib integration) or use **Armorer's Workshop** skins with the AW Addon.
<img src="images/GeckoAddon1.png" width="474" height="260"/>
<img src="images/GeckoAddon2.png" width="474" height="260"/>
---
### Addon Compatibility
CustomNPC+ has official addons that extend its functionality with other mods. Vault and Pixelmon are also supported natively for economy and scripting integration.
---
## Official CNPC+ Addons
### DBC Addon
Adds full compatibility between [Dragon Block C](https://www.curseforge.com/minecraft/mc-mods/dragon-block-c) and CustomNPC+. Extends abilities with ki-based attacks, adds DBC stat integration to conditions, and enables DBC model/animation support on NPCs.
[![CurseForge](https://img.shields.io/badge/CurseForge-F16436?style=flat-square&logo=curseforge&logoColor=white)](https://www.curseforge.com/minecraft/mc-mods/cnpc-dbc-addon)
[![Modrinth](https://img.shields.io/badge/Modrinth-00AF5C?style=flat-square&logo=modrinth&logoColor=white)](https://modrinth.com/mod/customnpc-plus-dbc-addon)
[![GitHub](https://img.shields.io/badge/GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/KAMKEEL/CustomNPC-DBC-Addon)
### Gecko Addon
Adds [GeckoLib](https://www.curseforge.com/minecraft/mc-mods/geckolib) support to CustomNPC+. Import Blockbench models directly into the mod and use them as custom NPC models with full animation support.
[![CurseForge](https://img.shields.io/badge/CurseForge-F16436?style=flat-square&logo=curseforge&logoColor=white)](https://www.curseforge.com/minecraft/mc-mods/cnpc-custom-model-addon)
[![Modrinth](https://img.shields.io/badge/Modrinth-00AF5C?style=flat-square&logo=modrinth&logoColor=white)](https://modrinth.com/mod/customnpc-plus-gecko-addon)
[![GitHub](https://img.shields.io/badge/GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/KAMKEEL/CustomNPC-Gecko-Addon)
### Armorer's Workshop Addon
Adds compatibility between [Armorer's Workshop](https://www.curseforge.com/minecraft/mc-mods/armorers-workshop) and CustomNPC+. Use Armorer's Workshop skins and equipment on NPCs with custom rotation, scaling, and animation support. Requires UniMixins.
[![CurseForge](https://img.shields.io/badge/CurseForge-F16436?style=flat-square&logo=curseforge&logoColor=white)](https://www.curseforge.com/minecraft/mc-mods/cnpc-armorers-workshop-addon)
[![Modrinth](https://img.shields.io/badge/Modrinth-00AF5C?style=flat-square&logo=modrinth&logoColor=white)](https://modrinth.com/mod/customnpc-plus-aw-addon)
---
## Other Projects
### MorePlayerModels+
An updated continuation of MorePlayerModels for 1.7.10 with backported GUIs, original parts and customizations, and permission support. Works alongside CustomNPC+. This is a replacement — do not install alongside the original MorePlayerModels.
[![CurseForge](https://img.shields.io/badge/CurseForge-F16436?style=flat-square&logo=curseforge&logoColor=white)](https://www.curseforge.com/minecraft/mc-mods/moreplayermodels-plus)
[![Modrinth](https://img.shields.io/badge/Modrinth-00AF5C?style=flat-square&logo=modrinth&logoColor=white)](https://modrinth.com/mod/moreplayermodels+)
[![GitHub](https://img.shields.io/badge/GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/KAMKEEL/MorePlayerModels-Plus)
### The Plugin Mod
Adds aesthetic weapons, artifacts, blocks, and apples for RPG and storytelling servers. Designed to pair with CustomNPC+ — use the included swords, spears, axes, medpacks, and decorative blocks to make your NPCs and worlds more diverse.
[![CurseForge](https://img.shields.io/badge/CurseForge-F16436?style=flat-square&logo=curseforge&logoColor=white)](https://www.curseforge.com/minecraft/mc-mods/the-plugin-mod)
[![Modrinth](https://img.shields.io/badge/Modrinth-00AF5C?style=flat-square&logo=modrinth&logoColor=white)](https://modrinth.com/mod/the-plugin-mod)
[![GitHub](https://img.shields.io/badge/GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/KAMKEEL/Plugin-Mod)
### HexText
Upgrades Minecraft's font renderer with full RGB hex color support, nested color spans, and animated text effects (glow, shake, wave). Works in signs, anvils, and chat. Requires UniMixins.
[![CurseForge](https://img.shields.io/badge/CurseForge-F16436?style=flat-square&logo=curseforge&logoColor=white)](https://www.curseforge.com/minecraft/mc-mods/hex-text)
[![Modrinth](https://img.shields.io/badge/Modrinth-00AF5C?style=flat-square&logo=modrinth&logoColor=white)](https://modrinth.com/mod/hex-text)
[![GitHub](https://img.shields.io/badge/GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/KAMKEEL/HexText)
### RPG Messenger
A Bukkit plugin for messageable NPCs, group chats, and RPG controller tools. Allows operators to create NPCs that players can message, and lets RPG controllers reply as NPCs in real-time.
[![GitHub](https://img.shields.io/badge/GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/KAMKEEL/RPGMessenger)
### CustomNPC+ Dark Mode
A resource pack that applies a clean dark theme to all CustomNPC+ GUIs.
[![GitHub](https://img.shields.io/badge/GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/KAMKEEL/CustomNPC-Plus-Dark-Mode)
---
## Configuration
CustomNPC+ has a modular configuration system:
| Config | Purpose |
|--------|---------|
| **Main** | Core mod settings |
| **Client** | Client-side display and UI preferences |
| **Script** | Scripting engine settings and client script toggle |
| **Market** | Currency, auction house, trader, and economy settings |
| **Energy** | Energy projectile settings (explosion block damage, dome item blacklist) |
| **Item** | Item and gun configuration |
| **Debug** | Debug logging and diagnostics |
| **Experimental** | Experimental features toggle |
---
## Building from Source
This project uses the [GTNH Gradle Convention](https://github.com/GTNewHorizons/ExampleMod1.7.10) build system with [RetroFuturaGradle](https://github.com/GTNewHorizons/RetroFuturaGradle).
```bash
git clone <repo-url>
git submodule update --init --recursive
./gradlew build
```
That's it. Submodules pull in the API sources, and `build` handles decompilation, compilation, and mixin embedding automatically.
**Run the game:**
```bash
./gradlew runClient # Launch client
./gradlew runServer # Launch server
```
**IDE Setup (IntelliJ):**
1. Import the project as a Gradle project
2. Run `./gradlew genIntellijRuns`
3. Add program arguments: `--tweakClass org.spongepowered.asm.launch.MixinTweaker --mixin customnpcs.mixins.json`
---
## Notice
I am not the original creator of CustomNPC. The original creator is [Noppes](https://github.com/Noppes). I have been permitted to update and develop my own branch version for 1.7.10. The original mod updated to the latest Minecraft versions can be found on [CurseForge](https://www.curseforge.com/minecraft/mc-mods/custom-npcs) and [kodevelopment](http://www.kodevelopment.nl/minecraft/customnpcs).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 337 KiB

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 787 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 MiB

@@ -67,6 +67,7 @@ import noppes.npcs.compat.PixelmonHelper;
import noppes.npcs.config.ConfigMain;
import noppes.npcs.config.LoadConfiguration;
import noppes.npcs.config.legacy.LegacyConfig;
import noppes.npcs.controllers.APIRegistry;
import noppes.npcs.controllers.AnimationController;
import noppes.npcs.controllers.AuctionController;
import noppes.npcs.controllers.BankController;
@@ -316,6 +317,8 @@ public class CustomNpcs {
new AddonManager();
new AttributeController();
new MagicController();
APIRegistry.Instance.register("CNPC+ API", "https://kamkeel.github.io/CustomNPC-Plus/");
}
@EventHandler
@@ -2,13 +2,15 @@ package noppes.npcs.client.gui;
import kamkeel.npcs.network.packets.request.script.NPCScriptPacket;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiConfirmOpenLink;
import net.minecraft.client.gui.GuiYesNo;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.StatCollector;
import noppes.npcs.NoppesStringUtils;
import noppes.npcs.client.NoppesUtil;
import noppes.npcs.client.gui.util.SubGuiAPISelect;
import noppes.npcs.client.gui.util.SubGuiConfirmLink;
import noppes.npcs.controllers.APIRegistry;
import noppes.npcs.client.gui.script.GuiScriptInterface;
import noppes.npcs.client.gui.util.GuiCustomScroll;
import noppes.npcs.client.gui.util.GuiMenuTopButton;
@@ -107,7 +109,7 @@ public class GuiScript extends GuiScriptInterface {
top.active = showScript;
addTopButton(top = new GuiMenuTopButton(14, top, "gui.settings"));
top.active = !showScript;
addTopButton(new GuiMenuTopButton(15, top, "gui.website"));
addTopButton(new GuiMenuTopButton(15, top, "gui.api"));
if (showScript) {
initScriptView();
@@ -285,9 +287,6 @@ public class GuiScript extends GuiScriptInterface {
NoppesUtil.openGUI(player, this);
if (!result)
return;
if (id == 0) {
openLink("https://kamkeel.github.io/CustomNPC-Plus/");
}
if (id == 101) {
getTextField(2).setText(NoppesStringUtils.getClipboardContents());
}
@@ -319,7 +318,12 @@ public class GuiScript extends GuiScriptInterface {
initGui();
}
if (guibutton.id == 15) {
displayGuiScreen(new GuiConfirmOpenLink(this, "https://kamkeel.github.io/CustomNPC-Plus/", 0, true));
if (APIRegistry.Instance.size() == 1) {
String url = APIRegistry.Instance.getEntries().values().iterator().next();
setSubGui(new SubGuiConfirmLink(url));
} else {
setSubGui(new SubGuiAPISelect());
}
}
if (guibutton.id == 16) {
close();
@@ -23,6 +23,7 @@ public class GuiHudEditor extends GuiScreen {
private final int HANDLE_SIZE = 10;
private final int RESET_POSITION_ID = 100;
private final int RESET_SIZE_ID = 104;
// IDs for cycle buttons.
private final int CYCLE_LEFT_ID = 101;
@@ -90,9 +91,11 @@ public class GuiHudEditor extends GuiScreen {
rowHeight += btn.height + spacingY;
}
}
// Always add the Reset Position button below the custom buttons.
GuiButton resetPos = new GuiButton(RESET_POSITION_ID, width / 2 - buttonWidth / 2, height - 70 - rowHeight, buttonWidth, 20, "Reset Position");
// Always add the Reset Position and Reset Size buttons below the custom buttons.
GuiButton resetSize = new GuiButton(RESET_SIZE_ID, startXRight, height - 70 - rowHeight, buttonWidth, 20, "Reset Size");
GuiButton resetPos = new GuiButton(RESET_POSITION_ID, startXLeft, height - 70 - rowHeight, buttonWidth, 20, "Reset Position");
buttonList.add(resetPos);
buttonList.add(resetSize);
}
// Now update (or add if missing) the cycle buttons at the very bottom.
@@ -149,10 +152,11 @@ public class GuiHudEditor extends GuiScreen {
int actualY = (int) (hud.posY / 100F * res.getScaledHeight());
int margin = 5;
int barWidth = 6; // same as WIDTH_BAR_SIZE
int barX = actualX + (int) (hud.overlayWidth * effectiveScale) + margin;
int barX = actualX + (int) ((hud.overlayWidth + margin) * effectiveScale);
int barY = actualY + (int) (((hud.overlayHeight - 20) / 2.0F) * effectiveScale);
int barHeight = (int) (20 * effectiveScale);
if (mouseX >= barX && mouseX <= barX + barWidth &&
int scaledBarWidth = Math.max(6, (int) (barWidth * effectiveScale));
if (mouseX >= barX && mouseX <= barX + scaledBarWidth &&
mouseY >= barY && mouseY <= barY + barHeight) {
resizingWidth = true;
selectedComponent = hud;
@@ -226,15 +230,22 @@ public class GuiHudEditor extends GuiScreen {
}
if (resizing) {
int currentMouseX = Mouse.getX() * width / mc.displayWidth;
int deltaX = currentMouseX - initialMouseX;
selectedComponent.scale = initialScale + deltaX;
int currentMouseY = scaledHeight - (Mouse.getY() * scaledHeight / mc.displayHeight);
int actualX = (int) (selectedComponent.posX / 100F * scaledWidth);
int actualY = (int) (selectedComponent.posY / 100F * scaledHeight);
// Use diagonal distance ratio so the resize handle tracks the mouse
float initDist = (float) Math.sqrt((double)(initialMouseX - actualX) * (initialMouseX - actualX) + (double)(initialMouseY - actualY) * (initialMouseY - actualY));
float currDist = (float) Math.sqrt((double)(currentMouseX - actualX) * (currentMouseX - actualX) + (double)(currentMouseY - actualY) * (currentMouseY - actualY));
if (initDist > 1) {
selectedComponent.scale = (int) (initialScale * currDist / initDist);
}
if (selectedComponent.scale < 50) selectedComponent.scale = 50;
if (selectedComponent.scale > 300) selectedComponent.scale = 300;
if (selectedComponent.scale > 500) selectedComponent.scale = 500;
}
if (resizingWidth && (selectedComponent instanceof CompassHudComponent)) {
int currentMouseX = Mouse.getX() * width / mc.displayWidth;
int delta = currentMouseX - initialWidthMouseX;
selectedComponent.overlayWidth = initialOverlayWidth + delta;
selectedComponent.overlayWidth = initialOverlayWidth + (int) (delta / effectiveScale);
if (selectedComponent.overlayWidth < 50) selectedComponent.overlayWidth = 50;
if (selectedComponent.overlayWidth > 500) selectedComponent.overlayWidth = 500;
}
@@ -267,6 +278,11 @@ public class GuiHudEditor extends GuiScreen {
selectedComponent.posY = 100F * centerY / res.getScaledHeight();
updateCustomButtons();
}
} else if (button.id == RESET_SIZE_ID) {
if (selectedComponent != null) {
selectedComponent.scale = 100;
updateCustomButtons();
}
} else if (button.id == CYCLE_LEFT_ID) {
cycleComponent(-1);
} else if (button.id == CYCLE_RIGHT_ID) {
@@ -1,429 +0,0 @@
package noppes.npcs.client.gui.script;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiConfirmOpenLink;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiYesNo;
import net.minecraft.client.gui.GuiYesNoCallback;
import net.minecraft.util.StatCollector;
import noppes.npcs.NoppesStringUtils;
import noppes.npcs.client.gui.util.GuiCustomScroll;
import noppes.npcs.client.gui.util.GuiMenuTopButton;
import noppes.npcs.client.gui.util.GuiNPCInterface;
import noppes.npcs.client.gui.util.GuiNpcButton;
import noppes.npcs.client.gui.util.GuiNpcLabel;
import noppes.npcs.client.gui.util.GuiNpcTextArea;
import noppes.npcs.client.gui.util.GuiScriptTextArea;
import noppes.npcs.client.gui.util.ICustomScrollListener;
import noppes.npcs.client.gui.util.ISubGuiListener;
import noppes.npcs.client.gui.util.ITextChangeListener;
import noppes.npcs.client.gui.util.SubGuiInterface;
import noppes.npcs.controllers.ScriptController;
import noppes.npcs.janino.JaninoScript;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class GuiJaninoScript extends GuiNPCInterface implements GuiYesNoCallback, ITextChangeListener, ICustomScrollListener, ISubGuiListener {
private GuiScriptTextArea textArea;
private int activeTab = 0;
public List<String> allExternalScripts = new ArrayList<>();
private final int scriptLimit = 1;
public String previousHookClicked = "";
// GUI display list
List<String> hooklist = new ArrayList<>();
// Map from display name (with parameters if overloaded) to Method
Map<String, Method> hookMap = new HashMap<>();
public final GuiScreen parent;
private final JaninoScript container;
public Runnable saveFunction;
public GuiJaninoScript(GuiScreen parent, JaninoScript handler) {
this.drawDefaultBackground = true;
this.closeOnEsc = true;
this.xSize = 420;
this.setBackground("menubg.png");
this.parent = parent;
this.container = handler;
createHookList();
String lang = handler.getLanguage();
allExternalScripts.addAll(ScriptController.Instance.getScripts(lang));
}
public GuiJaninoScript setSave(Runnable run) {
saveFunction = run;
return this;
}
public void createHookList() {
// Group all declared methods of the handler's type by their method name
Map<String, List<Method>> grouped = Arrays.stream(container.type.getDeclaredMethods())
.collect(Collectors.groupingBy(Method::getName));
for (Map.Entry<String, List<Method>> entry : grouped.entrySet()) {
List<Method> methods = entry.getValue();
// Sort overloads by number of parameters (ascending)
// This ensures the simplest overload comes first
methods.sort(Comparator.comparingInt(Method::getParameterCount));
boolean first = true;
for (Method m : methods) {
if (Modifier.isFinal(m.getModifiers()))
continue; //skip finals
String displayName = first ? m.getName() :
m.getName() + "(" + Arrays.stream(m.getParameterTypes())
.map(Class::getSimpleName)
.collect(Collectors.joining(", ")) + ")";
hooklist.add(displayName);
hookMap.put(displayName, m);
first = false;
}
}
}
public void initGui() {
this.ySize = (int) ((double) this.xSize * 0.56);
if ((double) this.ySize > (double) this.height * 0.95) {
this.ySize = (int) ((double) this.height * 0.95);
this.xSize = (int) ((double) this.ySize / 0.56);
}
this.bgScale = (float) this.xSize / 400.0F;
super.initGui();
this.guiTop += 10;
int yoffset = (int) ((double) this.ySize * 0.02);
GuiMenuTopButton top;
addTopButton(new GuiMenuTopButton(-1, guiLeft + xSize - 2, guiTop - 17, "X"));
this.addTopButton(top = new GuiMenuTopButton(0, this.guiLeft + 4, this.guiTop - 17, "gui.settings"));
int topXoffset = 0;
int topYoffset = 0;
if (this.container != null) {
this.addTopButton(top = new GuiMenuTopButton(1, top.xPosition + top.width + topXoffset, top.yPosition + topYoffset, "Script"));
} else {
this.addTopButton(new GuiMenuTopButton(this.scriptLimit, top.xPosition + top.width, top.yPosition, "+"));
}
top = this.getTopButton(this.activeTab);
if (top == null) {
this.activeTab = 0;
top = this.getTopButton(0);
}
top.active = true;
if (this.activeTab == 1) {
GuiCustomScroll hooks = new GuiCustomScroll(this, 1);
hooks.allowUserInput = false;
hooks.setSize(108, 198);
hooks.guiLeft = this.guiLeft - 110;
hooks.guiTop = this.guiTop + 14;
hooks.setUnsortedList(this.hooklist);
this.addScroll(hooks);
GuiNpcLabel hookLabel = new GuiNpcLabel(0, "script.hooks", hooks.guiLeft, this.guiTop + 5);
hookLabel.color = 11184810;
this.addLabel(hookLabel);
JaninoScript container = this.container;
if (textArea == null)
textArea = new GuiScriptTextArea(this, 2, this.guiLeft + 1 + yoffset, this.guiTop + yoffset, this.xSize - 108 - yoffset, (int) ((double) this.ySize * 0.96) - yoffset * 2, container == null ? "" : container.script);
else
textArea.init(this.guiLeft + 1 + yoffset, this.guiTop + yoffset, this.xSize - 108 - yoffset, (int) ((double) this.ySize * 0.96) - yoffset * 2, container == null ? "" : container.script);
textArea.enableCodeHighlighting();
textArea.setListener(this);
this.closeOnEsc(textArea::closeOnEsc);
this.addTextField(textArea);
int left1 = this.guiLeft + this.xSize - 104;
this.addButton(new GuiNpcButton(102, left1, this.guiTop + yoffset, 60, 20, "gui.clear"));
this.addButton(new GuiNpcButton(101, left1 + 61, this.guiTop + yoffset, 60, 20, "gui.paste"));
this.addButton(new GuiNpcButton(100, left1, this.guiTop + 21 + yoffset, 60, 20, "gui.copy"));
this.addButton(new GuiNpcButton(105, left1 + 61, this.guiTop + 21 + yoffset, 60, 20, "gui.remove"));
this.addButton(new GuiNpcButton(107, left1, this.guiTop + 66 + yoffset, 80, 20, "script.loadscript"));
// this.getButton(107).enabled = false;
GuiCustomScroll scroll = (new GuiCustomScroll(this, 0)).setUnselectable();
scroll.setSize(100, (int) ((double) this.ySize * 0.54) - yoffset * 2);
scroll.guiLeft = left1;
scroll.guiTop = this.guiTop + 88 + yoffset;
if (container != null)
scroll.setList(container.externalScripts);
this.addScroll(scroll);
} else {
GuiNpcTextArea var8 = new GuiNpcTextArea(2, this, this.guiLeft + 4 + yoffset, this.guiTop + 6 + yoffset, this.xSize - 160 - yoffset, (int) ((float) this.ySize * 0.92F) - yoffset * 2, this.getConsoleText());
var8.enabled = false;
this.addTextField(var8);
int var9 = this.guiLeft + this.xSize - 150;
this.addButton(new GuiNpcButton(100, var9, this.guiTop + 125, 60, 20, "gui.copy"));
this.addButton(new GuiNpcButton(102, var9, this.guiTop + 146, 60, 20, "gui.clear"));
this.addLabel(new GuiNpcLabel(1, "script.language", var9, this.guiTop + 15));
String containerLang = container.getLanguage();
this.addButton(new GuiNpcButton(103, var9 + 60, this.guiTop + 10, 80, 20, new String[]{containerLang.equals("Java") ? "Janino (JVM 8)" : containerLang},
this.getLanguageIndex()));
// this.getButton(103).enabled = !this.languages.isEmpty();
this.addLabel(new GuiNpcLabel(2, "gui.enabled", var9, this.guiTop + 36));
// Note: GuiJaninoScript is deprecated; enabled button disabled
this.addButton(new GuiNpcButton(104, var9 + 60, this.guiTop + 31, 50, 20, new String[]{"gui.no", "gui.yes"}, 0));
this.addButton(new GuiNpcButton(109, var9, this.guiTop + 78, 80, 20, "gui.website"));
this.addButton(new GuiNpcButton(112, var9 + 81, this.guiTop + 78, 80, 20, "gui.forum"));
this.addButton(new GuiNpcButton(110, var9, this.guiTop + 99, 80, 20, "script.apidoc"));
this.addButton(new GuiNpcButton(111, var9 + 81, this.guiTop + 99, 80, 20, "script.apisrc"));
}
this.xSize = 420;
this.ySize = 256;
}
public void customScrollClicked(int i, int j, int k, GuiCustomScroll scroll) {
String hook = scroll.getSelected();
if (this.previousHookClicked.equals(hook)) {
String addString = "";
if (!this.getTextField(2)
.getText()
.isEmpty()) {
addString = addString + "\n";
}
addString = addString + generateMethodStub(hookMap.get(hook));
this.getTextField(2)
.setText(this.getTextField(2)
.getText() + addString);
this.previousHookClicked = "";
} else {
this.previousHookClicked = hook;
}
}
public void confirmClicked(boolean flag, int i) {
if (flag) {
if (i == 0)
this.openLink("https://www.curseforge.com/minecraft/mc-mods/customnpc-plus");
else if (i == 1)
this.openLink("https://kamkeel.github.io/CustomNPC-Plus/");
else if (i == 2)
this.openLink("https://kamkeel.github.io/CustomNPC-Plus/");
else if (i == 3)
this.openLink("http://www.minecraftforge.net/forum/index.php/board,122.0.html");
if (i == 10) { //Remove
this.container.setScript("");
this.activeTab = 0;
} else if (i == 101) { //Paste
this.getTextField(2)
.setText(NoppesStringUtils.getClipboardContents());
this.setScript();
} else if (i == 102) { //Clear
if (this.activeTab == 1)
container.script = "";
else
this.container.clearConsole();
this.initGui();
}
}
this.displayGuiScreen(this);
}
protected void actionPerformed(GuiButton guibutton) {
if (guibutton.id == -1) {
close();
return;
}
if (guibutton.id == 0) {
this.setScript();
this.activeTab = 0;
this.initGui();
container.ensureCompiled();
}
if (guibutton.id == this.scriptLimit) {
// if (container.container == null)
//this.container.container = new ScriptContainer(this.container);
// else
// this.setScript();
this.activeTab = 1;
this.initGui();
}
if (guibutton.id == 109) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "https://kamkeel.github.io/CustomNPC-Plus/", 0, true));
} else if (guibutton.id == 110) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "https://github.com/KAMKEEL/CustomNPC-Plus-API", 1, true));
} else if (guibutton.id == 111) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "https://github.com/Noppes/CustomNPCsAPI", 2, true));
} else if (guibutton.id == 112) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "http://www.minecraftforge.net/forum/index.php/board,122.0.html", 3, true));
}
GuiYesNo confirmScreen;
//Copy
if (guibutton.id == 100) {
NoppesStringUtils.setClipboardContents(this.getTextField(2)
.getText());
}
//Paste
else if (guibutton.id == 101) {
GuiYesNo guiyesno = new GuiYesNo(this, StatCollector.translateToLocal("gui.paste"), StatCollector.translateToLocal("gui.sure"), 101);
this.displayGuiScreen(guiyesno);
}
//Clear
else if (guibutton.id == 102) {
confirmScreen = new GuiYesNo(this, StatCollector.translateToLocal("gui.clear"), StatCollector.translateToLocal("gui.sure"), 102);
this.displayGuiScreen(confirmScreen);
}
//Enable
else if (guibutton.id == 104) {
// Deprecated GUI - enabled is now per-handler, not per-unit
}
//Remove
else if (guibutton.id == 105) {
confirmScreen = new GuiYesNo(this, "", guibutton.displayString, 10);
this.displayGuiScreen(confirmScreen);
}
//Load Script
else if (guibutton.id == 107) {
setSubGui(new SubGuiSelectList(allExternalScripts, container.externalScripts, "All Scripts", "Selected Scripts"));
}
}
public void subGuiClosed(SubGuiInterface sub) {
if (sub instanceof SubGuiSelectList)
container.setExternalScripts(((SubGuiSelectList) sub).selected);
}
/**
* Generates a stub string for a given Method.
*/
public static String generateMethodStub(Method method) {
String mods = java.lang.reflect.Modifier.toString(method.getModifiers());
if (!mods.isEmpty())
mods += " ";
String returnTypeStr = method.getReturnType()
.getSimpleName();
String name = method.getName();
Map<String, Integer> typeCount = new HashMap<>();
String params = Arrays.stream(method.getParameters())
.map(p -> {
String typeName = p.getType()
.getSimpleName();
String baseName = Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1);
int count = typeCount.getOrDefault(baseName, 0) + 1;
typeCount.put(baseName, count);
// append number if same type of arg exists
String paramName = count == 1 ? baseName : baseName + (count - 1);
return typeName + " " + paramName;
})
.collect(Collectors.joining(", "));
String body;
Class<?> returnType = method.getReturnType();
if (returnType == void.class)
body = "";
else if (returnType.isPrimitive()) {
if (returnType == boolean.class)
body = " return false;";
else if (returnType == char.class)
body = " return '\\0';";
else
body = " return 0;";
} else {
body = " return null;";
}
return String.format("%s%s %s(%s) {\n%s\n}\n", mods, returnTypeStr, name, params, body);
}
private String getConsoleText() {
Map<Long, String> map = this.container.getConsole();
StringBuilder builder = new StringBuilder();
Iterator var3 = map.entrySet()
.iterator();
while (var3.hasNext()) {
Map.Entry<Long, String> entry = (Map.Entry) var3.next();
builder.insert(0, new Date(entry.getKey()) + entry.getValue() + "\n");
}
return builder.toString();
}
private int getLanguageIndex() {
return 0;
}
private void setScript() {
if (this.activeTab == 1) {
String text = this.getTextField(2)
.getText();
text = text.replace("\r\n", "\n");
text = text.replace("\r", "\n");
container.setScript(text);
}
}
public void save() {
if (saveFunction != null)
saveFunction.run();
else if (parent instanceof GuiNPCInterface)
((GuiNPCInterface) parent).save();
}
public void textUpdate(String text) {
// container.setScript(text);
}
public static GuiJaninoScript create(GuiScreen parent, JaninoScript script, int width, int height) {
GuiJaninoScript gui = new GuiJaninoScript(parent, script);
gui.setWorldAndResolution(Minecraft.getMinecraft(), width, height);
Minecraft.getMinecraft().currentScreen = gui;
gui.initGui();
return gui;
}
@Override
public void close() {
this.save();
this.setScript();
container.ensureCompiled();
parent.setWorldAndResolution(mc, width, height);
parent.initGui();
mc.currentScreen = parent;
}
}
@@ -11,7 +11,10 @@ import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.StatCollector;
import noppes.npcs.NoppesStringUtils;
import noppes.npcs.client.NoppesUtil;
import noppes.npcs.controllers.APIRegistry;
import noppes.npcs.client.gui.util.GuiCustomScroll;
import noppes.npcs.client.gui.util.SubGuiAPISelect;
import noppes.npcs.client.gui.util.SubGuiConfirmLink;
import noppes.npcs.client.gui.util.GuiMenuTopButton;
import noppes.npcs.client.gui.util.GuiNPCInterface;
import noppes.npcs.client.gui.util.GuiNpcButton;
@@ -127,6 +130,8 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
// Request data from server
if (handler instanceof IScriptHandlerPacket)
((IScriptHandlerPacket) handler).requestData();
else
gui.serverDataReceived = true; // No server data to wait for
return gui;
}
@@ -366,13 +371,11 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
}
this.addButton(new GuiNpcButton(109, var9, this.guiTop + 78, 80, 20, "gui.website"));
this.addButton(new GuiNpcButton(112, var9 + 81, this.guiTop + 78, 80, 20, "gui.forum"));
this.addButton(new GuiNpcButton(110, var9, this.guiTop + 99, 80, 20, "script.apidoc"));
this.addButton(new GuiNpcButton(111, var9 + 81, this.guiTop + 99, 80, 20, "script.apisrc"));
this.addButton(new GuiNpcButton(110, var9 + 81, this.guiTop + 78, 80, 20, "gui.api"));
// Disable all buttons until server data arrives
if (!serverDataReceived) {
for (int btnId : new int[]{100, 102, 103, 104, 106, 109, 110, 111, 112}) {
for (int btnId : new int[]{100, 102, 103, 104, 106, 109, 110}) {
GuiNpcButton btn = getButton(btnId);
if (btn != null) btn.enabled = false;
}
@@ -608,17 +611,6 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
this.openLink("https://www.curseforge.com/minecraft/mc-mods/customnpc-plus");
}
if (i == 1) {
this.openLink("https://kamkeel.github.io/CustomNPC-Plus/");
}
if (i == 2) {
this.openLink("https://kamkeel.github.io/CustomNPC-Plus/");
}
if (i == 3) {
this.openLink("http://www.minecraftforge.net/forum/index.php/board,122.0.html");
}
if (i == 10) {
handler.removeScriptUnit(this.activeTab - 1);
this.activeTab = 0;
@@ -714,15 +706,12 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
}
if (guibutton.id == 110) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "https://github.com/KAMKEEL/CustomNPC-Plus-API", 1, true));
}
if (guibutton.id == 111) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "https://github.com/Noppes/CustomNPCsAPI", 2, true));
}
if (guibutton.id == 112) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "http://www.minecraftforge.net/forum/index.php/board,122.0.html", 3, true));
if (APIRegistry.Instance.size() == 1) {
String url = APIRegistry.Instance.getEntries().values().iterator().next();
this.setSubGui(new SubGuiConfirmLink(url));
} else {
this.setSubGui(new SubGuiAPISelect());
}
}
if (guibutton.id == 100) {
@@ -0,0 +1,59 @@
package noppes.npcs.client.gui.util;
import net.minecraft.client.gui.GuiButton;
import noppes.npcs.controllers.APIRegistry;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
public class SubGuiAPISelect extends SubGuiInterface implements ISubGuiListener {
private final List<String> names;
private final List<String> urls;
public SubGuiAPISelect() {
LinkedHashMap<String, String> entries = APIRegistry.Instance.getEntries();
this.names = new ArrayList<>(entries.keySet());
this.urls = new ArrayList<>(entries.values());
this.xSize = 180;
this.ySize = 40 + names.size() * 24;
this.closeOnEsc = true;
}
@Override
public void initGui() {
super.initGui();
addLabel(new GuiNpcLabel(0, "gui.api", guiLeft + 10, guiTop + 10, 0xFFFFFF));
for (int i = 0; i < names.size(); i++) {
addButton(new GuiNpcButton(i, guiLeft + 10, guiTop + 28 + i * 24, 160, 20, names.get(i)));
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
drawGradientRect(guiLeft, guiTop, guiLeft + xSize, guiTop + ySize, 0xC0101010, 0xC0101010);
super.drawScreen(mouseX, mouseY, partialTicks);
}
@Override
protected void actionPerformed(GuiButton guibutton) {
int id = guibutton.id;
if (id >= 0 && id < urls.size()) {
setSubGui(new SubGuiConfirmLink(urls.get(id)));
}
}
@Override
public void subGuiClosed(SubGuiInterface subgui) {
if (subgui instanceof SubGuiConfirmLink && ((SubGuiConfirmLink) subgui).confirmed) {
close();
}
}
@Override
public void save() {
}
}
@@ -0,0 +1,61 @@
package noppes.npcs.client.gui.util;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.resources.I18n;
import noppes.npcs.NoppesStringUtils;
public class SubGuiConfirmLink extends SubGuiInterface {
private final String url;
public boolean confirmed = false;
public SubGuiConfirmLink(String url) {
this.url = url;
this.xSize = 260;
this.ySize = 120;
this.closeOnEsc = true;
}
@Override
public void initGui() {
super.initGui();
GuiNpcLabel titleLabel = new GuiNpcLabel(0, I18n.format("chat.link.confirmTrusted"), guiLeft, guiTop + 10, 0xFFFFFF);
titleLabel.center(xSize);
addLabel(titleLabel);
GuiNpcLabel urlLabel = new GuiNpcLabel(1, url, guiLeft, guiTop + 28, 0xCCCCFF);
urlLabel.center(xSize);
addLabel(urlLabel);
int btnWidth = 115;
int spacing = 10;
int totalWidth = btnWidth * 2 + spacing;
int startX = guiLeft + (xSize - totalWidth) / 2;
addButton(new GuiNpcButton(0, startX, guiTop + 52, btnWidth, 20, "chat.link.open"));
addButton(new GuiNpcButton(1, startX + btnWidth + spacing, guiTop + 52, btnWidth, 20, "chat.copy"));
addButton(new GuiNpcButton(2, guiLeft + (xSize - btnWidth) / 2, guiTop + 78, btnWidth, 20, "gui.cancel"));
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
drawGradientRect(0, 0, width, height, 0xC0101010, 0xC0101010);
super.drawScreen(mouseX, mouseY, partialTicks);
}
@Override
protected void actionPerformed(GuiButton guibutton) {
if (guibutton.id == 0) {
confirmed = true;
openLink(url);
} else if (guibutton.id == 1) {
confirmed = true;
NoppesStringUtils.setClipboardContents(url);
}
close();
}
@Override
public void save() {
}
}
@@ -87,10 +87,10 @@ public class ConfigClient {
public static float QuestOverlayX = 0;
public static Property QuestOverlayYProperty;
public static float QuestOverlayY = 31;
public static float QuestOverlayY = 14;
public static Property QuestOverlayScaleProperty;
public static int QuestOverlayScale = 223;
public static int QuestOverlayScale = 246;
public static Property QuestOverlayTextAlignProperty;
public static int QuestOverlayTextAlign = 0; // 0: Left, 1: Center, 2: Right
@@ -99,35 +99,35 @@ public class ConfigClient {
public static boolean CompassEnabled = true;
public static Property CompassOverlayXProperty;
public static float CompassOverlayX = 37;
public static float CompassOverlayX = 36;
public static Property CompassOverlayYProperty;
public static float CompassOverlayY = 0;
public static Property CompassOverlayScaleProperty;
public static int CompassOverlayScale = 276;
public static int CompassOverlayScale = 344;
public static Property CompassOverlayWidthProperty;
public static int CompassOverlayWidth = 200;
public static int CompassOverlayWidth = 158;
// Ability Hotbar HUD
public static Property AbilityHotbarEnabledProperty;
public static boolean AbilityHotbarEnabled = true;
public static Property AbilityHotbarXProperty;
public static float AbilityHotbarX = 3;
public static float AbilityHotbarX = 0;
public static Property AbilityHotbarYProperty;
public static float AbilityHotbarY = 50;
public static float AbilityHotbarY = 43;
public static Property AbilityHotbarScaleProperty;
public static int AbilityHotbarScale = 100;
public static int AbilityHotbarScale = 227;
public static Property AbilityHotbarHorizontalProperty;
public static boolean AbilityHotbarHorizontal = false;
public static Property AbilityHotbarAltTextureProperty;
public static boolean AbilityHotbarAltTexture = false;
public static boolean AbilityHotbarAltTexture = true;
// 1=Above(H)/Left(V), 2=Below(H)/Right(V)
public static Property AbilityHotbarTextPositionProperty;
@@ -179,11 +179,11 @@ public class ConfigClient {
QuestOverlayXProperty = config.get(HUD, "Quest Hud X", 0.0, "X position of the quest overlay.");
QuestOverlayX = (float) QuestOverlayXProperty.getDouble(0);
QuestOverlayYProperty = config.get(HUD, "Quest Hud Y", 31.0, "Y position of the quest overlay.");
QuestOverlayY = (float) QuestOverlayYProperty.getDouble(31);
QuestOverlayYProperty = config.get(HUD, "Quest Hud Y", 14.0, "Y position of the quest overlay.");
QuestOverlayY = (float) QuestOverlayYProperty.getDouble(14);
QuestOverlayScaleProperty = config.get(HUD, "Quest Hud Scale", 223, "Scale percentage of the quest overlay.");
QuestOverlayScale = QuestOverlayScaleProperty.getInt(223);
QuestOverlayScaleProperty = config.get(HUD, "Quest Hud Scale", 246, "Scale percentage of the quest overlay.");
QuestOverlayScale = QuestOverlayScaleProperty.getInt(246);
QuestOverlayTextAlignProperty = config.get(HUD, "Quest Hud Text Alignment", 0, "Text alignment in quest overlay (0: Left, 1: Center, 2: Right).");
QuestOverlayTextAlign = QuestOverlayTextAlignProperty.getInt(0);
@@ -200,7 +200,7 @@ public class ConfigClient {
CompassOverlayXProperty = config.get(
HUD,
"Compass Hud X",
37.0,
36.0,
"Horizontal position of compass overlay (0-100 percentage)"
);
CompassOverlayX = (float) CompassOverlayXProperty.getDouble();
@@ -216,19 +216,19 @@ public class ConfigClient {
CompassOverlayScaleProperty = config.get(
HUD,
"Compass Hud Scale",
276,
344,
"Scale percentage of compass overlay",
50,
300
500
);
CompassOverlayScale = CompassOverlayScaleProperty.getInt();
CompassOverlayWidthProperty = config.get(
HUD,
"Compass Hud Width",
200,
158,
"Base width of compass bar in pixels",
100,
50,
1000
);
CompassOverlayWidth = CompassOverlayWidthProperty.getInt();
@@ -237,19 +237,19 @@ public class ConfigClient {
AbilityHotbarEnabledProperty = config.get(HUD, "Ability Hotbar Enabled", true, "Enable Ability Hotbar HUD Component");
AbilityHotbarEnabled = AbilityHotbarEnabledProperty.getBoolean();
AbilityHotbarXProperty = config.get(HUD, "Ability Hotbar X", 3.0, "Horizontal position (0-100 percentage)");
AbilityHotbarXProperty = config.get(HUD, "Ability Hotbar X", 0.0, "Horizontal position (0-100 percentage)");
AbilityHotbarX = (float) AbilityHotbarXProperty.getDouble();
AbilityHotbarYProperty = config.get(HUD, "Ability Hotbar Y", 50.0, "Vertical position (0-100 percentage)");
AbilityHotbarYProperty = config.get(HUD, "Ability Hotbar Y", 43.0, "Vertical position (0-100 percentage)");
AbilityHotbarY = (float) AbilityHotbarYProperty.getDouble();
AbilityHotbarScaleProperty = config.get(HUD, "Ability Hotbar Scale", 100, "Scale percentage", 50, 300);
AbilityHotbarScaleProperty = config.get(HUD, "Ability Hotbar Scale", 227, "Scale percentage", 50, 500);
AbilityHotbarScale = AbilityHotbarScaleProperty.getInt();
AbilityHotbarHorizontalProperty = config.get(HUD, "Ability Hotbar Horizontal", false, "Display horizontally instead of vertically");
AbilityHotbarHorizontal = AbilityHotbarHorizontalProperty.getBoolean();
AbilityHotbarAltTextureProperty = config.get(HUD, "Ability Hotbar Alt Texture", false, "Use rounded square instead of circle slots");
AbilityHotbarAltTextureProperty = config.get(HUD, "Ability Hotbar Alt Texture", true, "Use rounded square instead of circle slots");
AbilityHotbarAltTexture = AbilityHotbarAltTextureProperty.getBoolean();
AbilityHotbarTextPositionProperty = config.get(HUD, "Ability Hotbar Text Position", 2, "Text label position (1=Above/Left, 2=Below/Right)");
@@ -0,0 +1,25 @@
package noppes.npcs.controllers;
import java.util.LinkedHashMap;
public class APIRegistry {
public static final APIRegistry Instance = new APIRegistry();
private final LinkedHashMap<String, String> entries = new LinkedHashMap<>();
public void register(String name, String url) {
entries.put(name, url);
}
public void unregister(String name) {
entries.remove(name);
}
public LinkedHashMap<String, String> getEntries() {
return entries;
}
public int size() {
return entries.size();
}
}
@@ -43,8 +43,8 @@ public class AnimationController implements IAnimationHandler {
// Built-in animations loaded from assets folder (read-only, keyed by name)
public HashMap<String, BuiltInAnimation> builtInAnimations = new HashMap<>();
private static final String BUILTIN_ANIMATIONS_PATH = "/assets/customnpcs/animations";
private static final String BUILTIN_ANIMATIONS_RESOURCE = "assets/customnpcs/animations";
private static final String BUILTIN_ANIMATIONS_PATH = "/assets/customnpcs/cnpc_animations";
private static final String BUILTIN_ANIMATIONS_RESOURCE = "assets/customnpcs/cnpc_animations";
public static AnimationController Instance = new AnimationController();
private int lastUsedID = 0;
@@ -75,7 +75,7 @@ public class AnimationController implements IAnimationHandler {
}
/**
* Load built-in animations from assets/customnpcs/animations/ folder.
* Load built-in animations from assets/customnpcs/cnpc_animations/ folder.
* These animations are read-only and accessed by name only (no IDs).
* Scans the folder directly instead of using a manifest.
*/
@@ -854,8 +854,7 @@ quest.tracking=Tracking
quest.alerts=Alerts
quest.noAlerts=No Alerts
script.apidoc=API Docs
script.apisrc=API Source
gui.api=API
script.scripts=Scripts
script.hooks=Hooks