Since there's like 0 information about this out there, I figured I'd be nice to all the skidz out there.
Let's begin.
First, we need a class called ModManager or something similar. This really just holds ArrayList<Mod>s that we used to make everyone's lives easier.
It'll look something like this...:
Code:
package com.godshawk.colony.mods;
import java.util.ArrayList;
import com.godshawk.colony.core.Colony;
public class ModManager {
public ArrayList<Mod> mods;
public ArrayList<Mod> worldMods;
public ArrayList<Mod> playerMods;
public ModManager( Colony c ) {
/*
* Eventually, I'll get around to having an EnumGUI
*/
mods = new ArrayList<Mod>( );
worldMods = new ArrayList<Mod>( );
playerMods = new ArrayList<Mod>( );
/**
-snip-
**/
// Main mod list
for ( Mod m : worldMods ) {
mods.add( m );
}
for ( Mod m : playerMods ) {
mods.add( m );
}
}
public void addMod( Mod m ) {
mods.add( m );
}
public void addWMod( Mod m ) {
worldMods.add( m );
}
public void addPMod( Mod m ) {
playerMods.add( m );
}
/**
* Gets hack by name. Ignores case. Returns matching mod; returns null
* otherwise. TODO Throw exception if mod==null?
*
* @param name
* @return
*/
public Mod getHackByName( String name ) {
for ( int i = 0; i < mods.size( ); i++ ) {
if ( name.equalsIgnoreCase( mods.get( i ).name ) ) {
return mods.get( i );
}
}
throw new NullPointerException( "Yo, hack " + name + " don't exist, bro. Da hale you thinkin'?" );
}
}
Mkay, so this should be self explanatory. We have ArrayList<Mod>s that hold our hacks in them. We use these for organizing our hacks in the right tabs. Yeah, it'd be neater to have it as "EnumGuiTab.[...]", but I haven't done that yet.
The getHackByName(...) method simply goes through our list of hacks to see if any of the hacks has the same name as the string passed in. If not, it throws an exception. Don't forget to "try {...} catch(...) {...}" it wherever you call it.
Now that we have that, we can move on to our GUI.
Okay, so all our GUI elements (buttons, windows, sliders, text, etc) will extend a base class that provides a lot of functionality. Let's call it Item.java.
Code:
package com.godshawk.colony.gui.api.components;
import com.godshawk.colony.core.Colony;
public abstract class Item {
/**
* Reference to the main class
*/
public Colony c;
/**
* Parent frame
*/
public Frame parent;
/**
* How to color it
*/
public int color;
/**
* Used for determining whether to do a gradient quad
*/
public int color2 = -1;
/**
* X position
*/
int x = 0;
/**
* Y position
*/
int y = 0;
/**
* Width
*/
int width = 0;
/**
* Height
*/
int height = 0;
/**
* Can it be dragged?
*/
public boolean draggable;
/**
* Is it currently being dragged?
*/
public boolean dragging;
/**
* Is it active? Ie do we activate the bound hack?
*/
public boolean active;
/**
* String to render on it
*/
public String text;
/**
* Drawing the thing is important too, you know?
*/
public abstract void draw( );
/**
* Updates. (Would) Handle[s] updating text,...
*/
public void update( ) {
draw( );
}
/**
* Drags the item
*/
public abstract void drag( int x, int y );
/**
* Sets the item's state to the given boolean. Args: State
*
* @param tof
*/
public void setActive( boolean tof ) {
this.active = tof;
}
/**
* Updates the text to the given string. Args: New text
*
* @param text
*/
public void updateText( String text ) {
this.text = text;
}
/**
* Returns true if the component is being dragged
*
* @return
*/
public boolean isDragging( ) {
if ( draggable ) {
if ( dragging ) {
return true;
}
}
return false;
}
/**
* Sets the dragging state. Args: New state
*/
public void setDragging( boolean state ) {
this.dragging = state;
}
/**
* Sets whether this Item is draggable
*
* @param tof
*/
public void setDraggable( boolean tof ) {
this.draggable = tof;
}
public boolean isDraggable( ) {
if ( draggable ) {
return true;
}
return false;
}
/**
* Handles mouse clicks
*
* @param x
* @param y
*/
public abstract void onClick( int x, int y );
/**
* Converts a hex value into a float[] we can use with glColor4f(...)
*
* @param hex
* @return
*/
public static float[ ] hexToRGBA( int hex )
{
float r = ( ( hex >> 16 ) & 255 ) / 255F;
float g = ( ( hex >> 8 ) & 255 ) / 255F;
float b = ( hex & 255 ) / 255F;
float a = ( ( hex >> 24 ) & 255 ) / 255F;
return new float[ ] { r, g, b, a };
}
/**
* Returns true if the mouse has clicked inside the component
*
* @param x
* @param y
* @return
*/
public boolean clickedInside( int x, int y ) {
if ( x > this.x ) {
if ( y > this.y ) {
if ( x < ( this.x + this.width ) ) {
if ( y < ( this.y + this.height ) ) {
return true;
}
}
}
}
return false;
}
public void setParent( Frame e, int x, int y ) {
this.parent = e;
this.x = x;
this.y = y;
}
/**
* Would, theoretically, be used for recoloring buttons when they're moused
* over.
*
* @param x
* @param y
* @return
*/
public abstract boolean mouseOver( int x, int y );
}
Most everything here is commented, so I'm just going to gloss over it. The only thing that might need explaining is the setParent(...){...} method. This method just tells the item which frame it's 'attached' to, and where to position itself. 'Course, now we need that frame class. Here it is:
Code:
package com.godshawk.colony.gui.api.components;
import java.util.ArrayList;
import com.godshawk.colony.core.Colony;
import com.godshawk.colony.gui.api.render.ModGuiUtils;
public class Frame extends Item {
public ArrayList<Item> toBeAdded = new ArrayList<Item>( );
public ArrayList<Item> toBeRemoved = new ArrayList<Item>( );
public ArrayList<Item> children = new ArrayList<Item>( );
boolean minimized = false;
public boolean pinnable = false;
public boolean pinned = false;
/**
* Should we clear labels? Only used for the console
*/
public boolean shouldClear = false;
int oldHeight = 0;
public Frame( Colony c, int x, int y, int w, int h, String s ) {
this( c, x, y, w, h, 0xff666666, s );
}
public Frame( Colony c, int x, int y, int w, int h, int color, String s ) {
this( c, x, y, w, h, color, -1, s );
}
public Frame( Colony c, int x, int y, int w, int h, int color, int color2, String s ) {
this.c = c;
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.oldHeight = h;
this.color = color;
this.color2 = color2;
this.text = s;
this.setDraggable( true );
}
@override
public void update( ) {
this.draw( );
}
@override
public void draw( ) {
// TODO Auto-generated method stub
if ( color2 > -1 ) {
ModGuiUtils.drawRect( x, y, x + width, y + oldHeight, color );
ModGuiUtils.drawRect( x, y, x + width, y + oldHeight, (int) ( color * 1.1 ) );
} else {
ModGuiUtils.drawGradientRect( x, y, x + width, y + oldHeight, color, color2 );
ModGuiUtils.drawRect( x, y + oldHeight, x + width, y + height, color2 );
}
/**
* Minimize button
*/
ModGuiUtils.drawFilledCircle( ( x + width ) - 8, y + 7, 2.5, 0xff00dd66 );
if ( minimized ) {
ModGuiUtils.drawFilledCircle( ( x + width ) - 8, y + 7, 2.5, 0xaa000000 );
}
if ( pinnable ) {
ModGuiUtils.drawFilledCircle( ( x + width ) - 16, y + 7, 2.5, 0xff72a9dc );
if ( pinned ) {
ModGuiUtils.drawFilledCircle( ( x + width ) - 16, y + 7, 2.5, 0xaa000000 );
}
}
ModGuiUtils.drawHorizontalLine( this.x + 2, ( this.x + this.width ) - 2, ( this.y + this.oldHeight ) - 6, 2, 0xff550055 );
Colony.instance.minecraft.fontRenderer.drawString( this.text, this.x + 3, this.y + 3, 0xff87b5ff );
if ( minimized ) {
this.height = oldHeight;
} else {
this.height = (int) ( oldHeight + ( ( oldHeight * this.children.size( ) ) / 1.4 ) + 5 );
}
if ( !minimized ) {
for ( Item e : children ) {
e.x = ( this.x ) + 3;
int offset = oldHeight;
offset /= 2;
offset += 4;
e.y = ( this.y ) + ( offset * ( this.children.indexOf( e ) + 1 ) ) + 10;
e.update( );
}
}
}
@override
public void drag( int x, int y ) {
// TODO Auto-generated method stub
if ( !this.isDraggable( ) || !this.isDragging( ) ) {
return;
}
this.x = x;
this.y = y;
}
@override
public void onClick( int x, int y ) {
// TODO Auto-generated method stub
// ModGuiUtils.drawFilledCircle( ( x + width ) - 8, y + 7, 2.5,
// 0xffaa0033 );
if ( this.clickedInside( x, y ) ) {
if ( x >= ( ( this.x + this.width ) - 8 - 2.5 ) ) {
if ( x <= ( ( ( this.x + this.width ) - 8 ) + 2.5 ) ) {
if ( y >= ( ( this.y + 7 ) - 2.5 ) ) {
if ( y <= ( this.y + 7 + 2.5 ) ) {
this.minimized = !this.minimized;
}
}
}
} else if ( x >= ( ( this.x + this.width ) - 16 - 2.5 ) ) {
if ( x <= ( ( ( this.x + this.width ) - 16 ) + 2.5 ) ) {
if ( y >= ( ( this.y + 7 ) - 2.5 ) ) {
if ( y <= ( this.y + 7 + 2.5 ) ) {
this.pinned = !this.pinned;
}
}
}
} else {
this.setDragging( true );
}
}
if ( !this.minimized ) {
for ( Item e : children ) {
e.onClick( x, y );
}
}
}
public void mouseUp( ) {
this.setDragging( false );
}
public void addChild( Item e ) {
children.add( e );
e.setParent( this, 0, 0 );
}
public void removeChild( Item e ) {
children.remove( e );
}
public void addLater( Item e ) {
toBeAdded.add( e );
}
public void removeLater( Item e ) {
toBeRemoved.add( e );
}
@override
public boolean clickedInside( int x, int y ) {
if ( x > this.x ) {
if ( y > this.y ) {
if ( x < ( this.x + this.width ) ) {
if ( y < ( this.y + this.oldHeight ) ) {
return true;
}
}
}
}
return false;
}
@override
public boolean mouseOver( int x, int y ) {
// TODO Auto-generated method stub
return false;
}
public void setPinnable( boolean state ) {
this.pinnable = state;
}
}
Right. This is kinda confusing, and not very commented. Let's break it down:
-public Frame( Colony c, int x, int y, int w, int h, String s )
This constructor just makes a very quick and basic frame. Used for testing and I never got around to changing it.
Parameters are: Main hack class, x, y, width, height, title
-public Frame( Colony c, int x, int y, int w, int h, int color, String s )
Alright, bit more advanced. Like the above, only a color parameter is added.
-public Frame( Colony c, int x, int y, int w, int h, int color, int color2, String s )
The actual constructor. Has 2 color parameters so that we can do that beautiful gradient thing you see in the GUI of my client.
The 'oldHeight' variable is used for handling minimizing. Yeah, there's probably a better way, but whatever. I threw this together at 2 in the morning.
-public void update()
The @override" annotation tells Java that this method overrides the update() method inherited from Item.java. All this does is call the frame's draw() method.
-public void draw()
Renders the frame onscreen. Breakdown:
*if(color2 > -1) {...} else {...}
This is used to determine whether we should do the gradient GUI. color2 defaults to -1 (As set in Item.java), so this always works.
*if(minimized) {...}
Change what the minimize button looks like when it's minimized. In this case, make it darker.
*if(pinnable) {...}
Same story as above.
*if(minimized) {...}
Handles height for minimized and not-minimized states.
*if(!minimized) {for(Item e : children) {...} }
Updates the child items.
Everything else should be simple to understand.
Now we need the child items that the frames can hold. The examples that I'll give are buttons and labels. First the buttons...
Code:
package com.godshawk.colony.gui.api.components;
import com.godshawk.colony.core.Colony;
import com.godshawk.colony.gui.api.render.ModGuiUtils;
import com.godshawk.colony.mods.Mod;
public class Button extends Item {
Frame parent;
Mod m = null;
int oldColor = 0;
public Button( String s, int color, int color2 ) {
this( s, color, color2, null );
}
public Button( String s, int color, int color2, Mod m ) {
this.text = s;
this.color = color;
this.color2 = color2;
this.oldColor = color2;
this.m = m;
}
@override
public void draw( ) {
// TODO Auto-generated method stub
ModGuiUtils.drawBorderedRect( x, y, x + width, y + height, 2, color, 0x77000077 );
Colony.instance.minecraft.fontRenderer.drawString( text, x + 2, y + 2, color2 );
}
@override
public void update( ) {
if ( this.m != null ) {
this.text = this.m.name;
if ( m.isActive( ) ) {
color2 = 0x00ff00;
} else {
color2 = oldColor;
}
}
this.draw( );
}
@override
public void drag( int x, int y ) {
// TODO Auto-generated method stub
return;
}
@override
public void onClick( int x, int y ) {
// TODO Auto-generated method stub
if ( this.clickedInside( x, y ) ) {
if ( this.m != null ) {
this.m.toggle( );
} else {
return;
}
}
}
@override
public void setParent( Frame e, int x, int y ) {
this.parent = e;
this.x = x;
this.y = y;
}
public void setWidth( int w ) {
this.width = w;
}
public void setHeight( int h ) {
this.height = h;
}
@override
public boolean mouseOver( int x, int y ) {
if ( x >= this.x ) {
if ( x <= ( this.x + this.width ) ) {
if ( y >= this.y ) {
if ( y <= ( this.y + this.height ) ) {
return true;
}
}
}
}
return false;
}
}
Bear in mind that this button will only be used for toggling hacks. You want it to do something else, you gotta change it yourself.
And if you don't get this..... Well, I recommend learning some more Java. OOP is NOT for the faint of heart/noobs/skidz.
Labels:
Code:
package com.godshawk.colony.gui.api.components;
import com.godshawk.colony.core.Colony;
public class Label extends Item {
Frame parent;
public Label( String s, int color ) {
this.text = s;
this.color = color;
}
@override
public void update( ) {
this.draw( );
}
@override
public void draw( ) {
// TODO Auto-generated method stub
Colony.instance.minecraft.fontRenderer.drawString( text, x + 3, y + 3, color );
}
@override
public void drag( int x, int y ) {
// TODO Auto-generated method stub
return;
}
@override
public void onClick( int x, int y ) {
// TODO Auto-generated method stub
return;
}
@override
public void setParent( Frame e, int x, int y ) {
this.parent = e;
this.x = x;
this.y = y;
}
@override
public boolean mouseOver( int x, int y ) {
// TODO Auto-generated method stub
return false;
}
}
That's it. Very, very, very simple.
Now to pull it all together. We need a GuiScreen that holds all the items we make, so that it can be rendered.
Here's an example one:
Code:
package com.godshawk.colony.gui.api.components;
import java.util.ArrayList;
import net.minecraft.src.Direction;
import net.minecraft.src.GuiScreen;
import net.minecraft.src.MathHelper;
import net.minecraft.src.TcpConnection;
import net.minecraft.src.Timer;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.GL11;
import com.godshawk.colony.core.Colony;
import com.godshawk.colony.gui.api.render.ModGuiUtils;
import com.godshawk.colony.gui.api.testing.TestEntityRenderer;
import com.godshawk.colony.mods.Mod;
public class TestGui extends GuiScreen {
public ArrayList<Frame> frames;
public TestGui( ) {
frames = new ArrayList<Frame>( );
initFrames( );
for ( Frame e : frames ) {
e.minimized = true;
}
}
@override
public boolean doesGuiPauseGame( ) {
return false;
}
@override
public void initGui( ) {
Keyboard.enableRepeatEvents( true );
}
@override
public void onGuiClosed( ) {
Keyboard.enableRepeatEvents( true );
}
@override
protected void keyTyped( char c, int i ) {
if ( i == 1 ) {
mc.displayGuiScreen( null );
return;
}
for ( Frame e : this.frames ) {
for ( Item t : e.children ) {
if ( t instanceof TextArea ) {
( (TextArea) t ).keyPressed( i );
}
}
}
}
@override
public void drawScreen( int i, int j, float f ) {
for ( Frame e : frames ) {
e.update( );
if ( e.toBeAdded.size( ) > 0 ) {
for ( Item q : e.toBeAdded ) {
e.addChild( q );
}
e.toBeAdded.clear( );
}
if ( e.toBeRemoved.size( ) > 0 ) {
for ( Item q : e.toBeRemoved ) {
e.removeChild( q );
}
e.toBeRemoved.clear( );
}
}
this.mouseDragged( i, j );
}
@override
protected void mouseClicked( int i, int j, int k ) {
for ( Frame e : frames ) {
e.onClick( i, j );
}
}
public void mouseDragged( int i, int j )
{
for ( Frame e : frames ) {
int q = ( e.x - i ) / 2;
int r = ( e.y - j ) / 2;
q += i - ( e.width / 4 );
r += j - ( e.oldHeight / 4 );
e.drag( q, r );
}
}
@override
protected void mouseMovedOrUp( int par1, int par2, int par3 ) {
for ( Frame e : frames ) {
e.mouseUp( );
}
}
public void makeWorldFrame( ) {
Frame wFrame = new Frame( Colony.instance, 10, 10, 120, 20, 0xff550055, 0xaa000000, "World" );
for ( Mod m : Colony.instance.mmanager.worldMods ) {
Button b = new Button( m.name, 0xff000077, 0xffffff, m );
b.setWidth( wFrame.width - 6 );
b.setHeight( wFrame.oldHeight - 8 );
wFrame.addChild( b );
}
wFrame.setPinnable( false );
addFrame( wFrame );
}
public void makePlayerFrame( ) {
Frame pFrame = new Frame( Colony.instance, 130, 10, 120, 20, 0xff550055, 0xaa000000, "Player" );
for ( Mod m : Colony.instance.mmanager.playerMods ) {
Button b = new Button( m.name, 0xff000077, 0xffffff, m );
b.setWidth( pFrame.width - 6 );
b.setHeight( pFrame.oldHeight - 8 );
pFrame.addChild( b );
}
addFrame( pFrame );
}
public void makeInfoFrame( ) {
final Frame iFrame = new Frame( Colony.instance, 250, 30, 120, 20, 0xff550055, 0xaa000000, "Player Info" ) {
@override
public void update( ) {
this.draw( );
String dim = "Overworld";
String dir = "NORTH";
int d = mc.thePlayer.dimension;
if ( d == -1 ) {
dim = "Nether";
}
if ( d == 0 ) {
dim = "Overworld";
}
if ( d == 1 ) {
dim = "The End";
}
int var24 = MathHelper.floor_double( ( ( mc.thePlayer****tationYaw * 4.0F ) / 360.0F ) + 0.5D ) & 3;
dir = Direction.directions [ var24 ];
try {
children.clear( );
addChild( new Label( "User: " + Colony.instance.minecraft.thePlayer.username, 0xffffff ) );
addChild( new Label( "FPS: " + mc.debugFPS, 0xffffff ) );
addChild( new Label( "Lagg: " + TcpConnection.field_74490_x, 0xffffff ) );
addChild( new Label( "X: " + (int) Colony.instance.minecraft.thePlayer.posX, 0xffffff ) );
addChild( new Label( "Y: " + (int) Colony.instance.minecraft.thePlayer.posY, 0xffffff ) );
addChild( new Label( "Z: " + (int) Colony.instance.minecraft.thePlayer.posZ, 0xffffff ) );
addChild( new Label( "Dimension: " + dim, 0xffffff ) );
addChild( new Label( "Facing " + dir, 0xffffff ) );
addChild( new Label( "Timer: " + Timer.timerSpeed, 0xffffff ) );
addChild( new Label( "", 0xffffff ) );
} catch ( Exception e ) {
e.printStackTrace( );
}
}
@override
public void draw( ) {
// TODO Auto-generated method stub
if ( color2 > -1 ) {
ModGuiUtils.drawRect( x, y, x + width, y + oldHeight, color );
ModGuiUtils.drawRect( x, y, x + width, y + oldHeight, (int) ( color * 1.1 ) );
} else {
ModGuiUtils.drawGradientRect( x, y, x + width, y + oldHeight, color, color2 );
ModGuiUtils.drawRect( x, y + oldHeight, x + width, y + height, color2 );
}
/**
* Minimize button
*/
ModGuiUtils.drawFilledCircle( ( x + width ) - 8, y + 7, 2.5, 0xff00dd66 );
if ( minimized ) {
ModGuiUtils.drawFilledCircle( ( x + width ) - 8, y + 7, 2.5, 0xaa000000 );
}
if ( pinnable ) {
ModGuiUtils.drawFilledCircle( ( x + width ) - 16, y + 7, 2.5, 0xff72a9dc );
if ( pinned ) {
ModGuiUtils.drawFilledCircle( ( x + width ) - 16, y + 7, 2.5, 0xaa000000 );
}
}
ModGuiUtils.drawHorizontalLine( this.x + 2, ( this.x + this.width ) - 2, ( this.y + this.oldHeight ) - 6, 2, 0xff550055 );
Colony.instance.minecraft.fontRenderer.drawString( this.text, this.x + 3, this.y + 3, 0xff87b5ff );
if ( minimized ) {
this.height = oldHeight;
} else {
this.height = (int) ( oldHeight + ( ( oldHeight * this.children.size( ) ) / 1.4 ) + 5 ) / 2;
}
if ( !minimized ) {
GL11.glPushMatrix( );
GL11.glScaled( 0.5, 0.5, 0.5 );
for ( Item e : children ) {
e.x = ( ( this.x ) + 3 ) * 2;
int offset = oldHeight;
offset /= 8;
offset += 4;
e.y = ( ( this.y ) + ( offset * ( this.children.indexOf( e ) + 1 ) ) + 15 ) * 2;
e.update( );
}
GL11.glScaled( 1, 1, 1 );
GL11.glPopMatrix( );
}
}
};
iFrame.setPinnable( true );
addFrame( iFrame );
}
public void addFrame( Frame e ) {
frames.add( e );
}
public void initFrames( ) {
makeWorldFrame( );
makePlayerFrame( );
makeInfoFrame( );
}
}
We start by having a *public* ArrayList of frames. This is VERY IMPORTANT.
The constructor just initializes and minimizes all the frames. Most of the rest is standard GuiScreen stuff, but adapted to fit our needs. Ie, the drawScreen(...) method updates and draws the frames and the children of each frame, the mouse methods deal with the frames, etc.
The nice thing is that you're able to override methods of the Frame class while instantiating it; you can see an example of that in the info frame.
The reason we needed the ModManager class is to make this:
Code:
public void makeWorldFrame( ) {
Frame wFrame = new Frame( Colony.instance, 10, 10, 120, 20, 0xff550055, 0xaa000000, "World" );
for ( Mod m : Colony.instance.mmanager.worldMods ) {
Button b = new Button( m.name, 0xff000077, 0xffffff, m );
b.setWidth( wFrame.width - 6 );
b.setHeight( wFrame.oldHeight - 8 );
wFrame.addChild( b );
}
wFrame.setPinnable( false );
addFrame( wFrame );
}
public void makePlayerFrame( ) {
Frame pFrame = new Frame( Colony.instance, 130, 10, 120, 20, 0xff550055, 0xaa000000, "Player" );
for ( Mod m : Colony.instance.mmanager.playerMods ) {
Button b = new Button( m.name, 0xff000077, 0xffffff, m );
b.setWidth( pFrame.width - 6 );
b.setHeight( pFrame.oldHeight - 8 );
pFrame.addChild( b );
}
addFrame( pFrame );
}
work. These two methods go through the ArrayList<mod>s that we set up earlier and add the mods from those automatically. Now, if you want to add a hack, you just write it up and then add it in the ModManager, and it's automatically added to the GUI.
That public ArrayList of Frames that we made is important. We can use it to render pinned frames onscreen like this:
Code:
public void updatePinnedFrames( ) {
if ( ( minecraft.currentScreen == null ) || ( minecraft.currentScreen == (Gui) minecraft.ingameGUI ) ) {
for ( Frame frame : modgui.frames ) {
if ( frame.pinned && frame.pinnable ) {
frame.update( );
}
}
}
}
See? Very easy.
Questions? Comments? Concerns? Leave a reply!