diff --git a/sigIRCv2.jar b/sigIRCv2.jar index b66c692..c72dca1 100644 Binary files a/sigIRCv2.jar and b/sigIRCv2.jar differ diff --git a/src/sig/MyPanel.java b/src/sig/MyPanel.java index e907573..7a1732a 100644 --- a/src/sig/MyPanel.java +++ b/src/sig/MyPanel.java @@ -39,6 +39,7 @@ public class MyPanel extends JPanel implements MouseListener, ActionListener, Mo final public static Font programFont = new Font(sigIRC.messageFont,0,24); final public static Font userFont = new Font(sigIRC.usernameFont,0,16); final public static Font smallFont = new Font(sigIRC.touhoumotherConsoleFont,0,12); + final public static Font rabiRibiMoneyDisplayFont = new Font("CP Font",0,16); public int lastMouseX = 0; public int lastMouseY = 0; diff --git a/src/sig/modules/RabiRibi/Entity.java b/src/sig/modules/RabiRibi/Entity.java index 49ab461..2a4bcec 100644 --- a/src/sig/modules/RabiRibi/Entity.java +++ b/src/sig/modules/RabiRibi/Entity.java @@ -2,6 +2,9 @@ package sig.modules.RabiRibi; import java.awt.Point; import java.lang.reflect.Field; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import com.sun.jna.Memory; import com.sun.jna.Pointer; @@ -25,6 +28,9 @@ public class Entity { long pointer; int color = 0; public EntityMarker marker; + public static int taskDone=1; //1 = Free, 0 = In Progress, -1 = Stop all current processes + + boolean killed=false; public Entity(long memoryOffset, int uuid, RabiRibiModule parent) { this.parent=parent; @@ -33,7 +39,7 @@ public class Entity { this.marker = new EntityMarker((int)x,(int)y,(int)x,(int)y,this,parent); UpdateValues(); this.active = readIntFromMemoryOffset(MemoryOffset.ENTITY_ISACTIVE, pointer)==1 && - id!=0 && id!=1 && maxhp!=0; + /*id!=0 && id!=1 &&*/ maxhp!=0; if (this.active) { parent.overlay.objects.add(this.marker); } @@ -83,6 +89,14 @@ public class Entity { if (this.animation!=-9999) { this.hp = readIntFromMemoryOffset(MemoryOffset.ENTITY_HP, pointer); } else { + if (active && !killed) { + killed=true; + if (taskDone==1 && !RabiUtils.isGamePaused()) { + RetrieveMoneyValueForLookupData(id,color); + } + EntityLookupData data = EntityLookupData.getEntityLookupData(id, color); + data.increaseKills(1); + } this.hp = 0; } this.maxhp = readIntFromMemoryOffset(MemoryOffset.ENTITY_MAXHP, pointer); @@ -92,6 +106,11 @@ public class Entity { this.marker.setTarget(parent.overlay.getScreenPosition(x,y)); } + private void RetrieveMoneyValueForLookupData(int id, int color) { + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleWithFixedDelay(new MoneyUpdateTask(scheduler,id,color,parent),500,500,TimeUnit.MILLISECONDS); + } + public int getUniqueID() { return uuid; } @@ -141,4 +160,60 @@ public class Entity { sb.append(")"); return sb.toString(); } + + class MoneyUpdateTask implements Runnable{ + ScheduledExecutorService scheduler; + RabiRibiModule parent; + int prev_money_val = -1; + int starting_money_val = 0; + int id, col; + int checkcount=0; + + MoneyUpdateTask(ScheduledExecutorService scheduler, int id, int col, RabiRibiModule parent) { + this.scheduler=scheduler; + this.parent=parent; + UpdateMoney(); + starting_money_val = prev_money_val; + this.id=id; + this.col=col; + //System.out.println("Starting Money Value: "+starting_money_val); + } + + private void UpdateMoney() { + prev_money_val = parent.readIntFromMemory(MemoryOffset.MONEY); + } + + @Override + public void run() { + if (Entity.taskDone==-1) { + System.out.println("Quitting early, killed an extra enemy."); + Entity.taskDone=1; + scheduler.shutdownNow(); + return; + } + if (RabiUtils.isGamePaused()) { + return; + } + int current_money = parent.readIntFromMemory(MemoryOffset.MONEY); + if (current_money==prev_money_val && (current_money!=starting_money_val || checkcount>5)) { + //System.out.println("Money Value matches, adding "+(current_money-starting_money_val)+" to "+lookup_data+" with ID "+id+","+color); + String hashcode = EntityLookupData.getHashCode(id, color); + if (parent.lookup_table.containsKey(hashcode)) { + EntityLookupData lookup_data = parent.lookup_table.get(hashcode); + lookup_data.setMoney(lookup_data.getMoney()+(current_money-starting_money_val)); + } else { + EntityLookupData lookup_data = new EntityLookupData(current_money-starting_money_val); + parent.lookup_table.put(hashcode,lookup_data); + parent.setStatusMessage("Adding "+(current_money-starting_money_val)+" to hash ID "+id+","+color); + } + EntityLookupData.saveEntityLookupData(parent.lookup_table); + Entity.taskDone=1; + scheduler.shutdownNow(); + } else { + checkcount++; + UpdateMoney(); + } + } + + } } diff --git a/src/sig/modules/RabiRibi/EntityLookupData.java b/src/sig/modules/RabiRibi/EntityLookupData.java new file mode 100644 index 0000000..4599469 --- /dev/null +++ b/src/sig/modules/RabiRibi/EntityLookupData.java @@ -0,0 +1,159 @@ +package sig.modules.RabiRibi; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import sig.sigIRC; +import sig.modules.RabiRibiModule; +import sig.utils.FileUtils; + +public class EntityLookupData { + final static String RABIRIBI_DIR = sigIRC.BASEDIR+"sigIRC/rabi-ribi/"; + final static String LOOKUP_DATA_FILE = RABIRIBI_DIR+"lookupdata.txt"; + int en=-1; + int kills=0; + public static RabiRibiModule parent; + + public EntityLookupData(){ + }; + + public EntityLookupData(int en){ + this(); + this.en=en; + }; + + public EntityLookupData(String[] parse_string){ + this(); + int i=1; + if (parse_string.length>=i++) { + this.en = Integer.parseInt(parse_string[0]); + } + if (parse_string.length>=i++) { + this.kills = Integer.parseInt(parse_string[1]); + } + }; + + public static int getMoney(int id, int color) { + String hashcode = EntityLookupData.getHashCode(id, color); + System.out.println("Hashcode is "+hashcode); + if (parent.lookup_table.containsKey(hashcode)) { + return parent.lookup_table.get(hashcode).getMoney(); + } else { + return -1; + } + } + + public int getMoney() { + return en; + } + + public void setMoney(int money) { + en = money; + } + + public int getKills() { + return kills; + } + + public void increaseKills(int amt) { + kills += amt; + } + + public String getSaveString() { + StringBuilder sb = new StringBuilder(); + sb.append(en); + sb.append(","); + sb.append(kills); + sb.append(","); + return sb.toString(); + } + + public static EntityLookupData getEntityLookupData(int id, int color) { + String hashcode = EntityLookupData.getHashCode(id, color); + if (parent.lookup_table.containsKey(hashcode)) { + return parent.lookup_table.get(hashcode); + } else { + EntityLookupData data = new EntityLookupData(0); + parent.lookup_table.put(getHashCode(id,color), data); + return data; + } + } + + public static String getHashCode(int id, int color) { + StringBuilder sb = new StringBuilder(); + sb.append(id); + sb.append("_"); + sb.append(color); + sb.append("_"); + sb.append(parent.readIntFromMemory(MemoryOffset.GAME_DIFFICULTY)); + sb.append("_"); + sb.append(parent.readIntFromMemory(MemoryOffset.GAME_LOOP)); + return sb.toString(); + } + + public static void saveEntityLookupData(HashMap vals) { + File dir = new File(RABIRIBI_DIR); + if (!dir.exists()) { + dir.mkdirs(); + } + List data = new ArrayList(); + for (String s : vals.keySet()) { + EntityLookupData lookup_data = vals.get(s); + data.add(s+":"+lookup_data.getSaveString()); + } + FileUtils.writetoFile(data.toArray(new String[data.size()]), LOOKUP_DATA_FILE); + } + + public static void loadEntityLookupData(HashMap map) { + File file = new File(LOOKUP_DATA_FILE); + map.clear(); + if (file.exists()) { + String[] data = FileUtils.readFromFile(LOOKUP_DATA_FILE); + for (String s : data) { + String[] key_split = s.split(":"); + String[] split = key_split[1].split(","); + + EntityLookupData lookup_data = new EntityLookupData( + split + ); + + map.put(key_split[0], + lookup_data + ); + } + } + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(this.getClass().getName()+"("); + boolean first=false; + for (Field f : this.getClass().getDeclaredFields()) { + //if (!ReflectUtils.isCloneable(f)) { + if (!first) { + try { + sb.append(f.getName()+"="+f.get(this)); + first=true; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } else { + try { + sb.append(","+f.getName()+"="+f.get(this)); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + //} + } + sb.append(")"); + return sb.toString(); + } +} diff --git a/src/sig/modules/RabiRibi/MemoryOffset.java b/src/sig/modules/RabiRibi/MemoryOffset.java index 7e02840..adaae00 100644 --- a/src/sig/modules/RabiRibi/MemoryOffset.java +++ b/src/sig/modules/RabiRibi/MemoryOffset.java @@ -33,7 +33,10 @@ public enum MemoryOffset { ENTITY_XPOS(0xC), ENTITY_YPOS(0x10), ENTITY_COLOR(0x1C), - TRANSITION_COUNTER(0xA7661C) + TRANSITION_COUNTER(0xA7661C), + + GAME_DIFFICULTY(0xD64338), + GAME_LOOP(0xD6D05C), ; long offset; diff --git a/src/sig/modules/RabiRibi/Overlay.java b/src/sig/modules/RabiRibi/Overlay.java index 1e3669b..3299f67 100644 --- a/src/sig/modules/RabiRibi/Overlay.java +++ b/src/sig/modules/RabiRibi/Overlay.java @@ -4,6 +4,7 @@ import java.awt.Graphics; import java.awt.Point; import java.awt.geom.Point2D; import java.util.ArrayList; +import java.util.ConcurrentModificationException; import java.util.List; import javax.swing.SwingUtilities; @@ -41,8 +42,12 @@ public class Overlay { xspd = camera_xpos-prev_camera_xpos; yspd = camera_ypos-prev_camera_ypos; - for (SmoothObject so : objects) { - so.run(); + try { + for (SmoothObject so : objects) { + so.run(); + } + } catch (ConcurrentModificationException e) { + } /*int new_xcoord,new_ycoord; @@ -107,6 +112,7 @@ public class Overlay { starting_camera_x = camera_xpos; starting_camera_y = camera_ypos; }*/ + //System.out.println("Objects: "+objects.size()); } public Point.Double getScreenPosition(float xpos, float ypos) { @@ -133,8 +139,12 @@ public class Overlay { public void draw(Graphics g) { if (parent.readIntFromMemory(MemoryOffset.TRANSITION_COUNTER)<300) { - for (SmoothObject so : objects) { - so.draw(g); + try { + for (SmoothObject so : objects) { + so.draw(g); + } + } catch (ConcurrentModificationException e) { + } } } diff --git a/src/sig/modules/RabiRibi/RabiUtils.java b/src/sig/modules/RabiRibi/RabiUtils.java new file mode 100644 index 0000000..e42b4aa --- /dev/null +++ b/src/sig/modules/RabiRibi/RabiUtils.java @@ -0,0 +1,11 @@ +package sig.modules.RabiRibi; + +import sig.modules.RabiRibiModule; + +public class RabiUtils { + public static RabiRibiModule module; + + public static boolean isGamePaused() { + return module.readIntFromMemory(MemoryOffset.TRANSITION_COUNTER)>=300; + } +} diff --git a/src/sig/modules/RabiRibi/SmoothObject.java b/src/sig/modules/RabiRibi/SmoothObject.java index 9c2b969..077fdd5 100644 --- a/src/sig/modules/RabiRibi/SmoothObject.java +++ b/src/sig/modules/RabiRibi/SmoothObject.java @@ -32,7 +32,7 @@ public class SmoothObject { return new Point(targetx,targety); } - public void setPosition(Point position) { + public void setPosition(Point.Double position) { x = (int)position.getX(); y = (int)position.getY(); } diff --git a/src/sig/modules/RabiRibiModule.java b/src/sig/modules/RabiRibiModule.java index 3e13558..ec0d48b 100644 --- a/src/sig/modules/RabiRibiModule.java +++ b/src/sig/modules/RabiRibiModule.java @@ -2,13 +2,20 @@ package sig.modules; import java.awt.Color; import java.awt.Graphics; +import java.awt.Point; import java.awt.event.KeyEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Arrays; +import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import javax.swing.SwingUtilities; import com.sun.jna.Memory; import com.sun.jna.Pointer; @@ -19,9 +26,12 @@ import com.sun.jna.platform.win32.WinNT.HANDLE; import sig.Module; import sig.sigIRC; import sig.modules.RabiRibi.Entity; +import sig.modules.RabiRibi.EntityLookupData; import sig.modules.RabiRibi.MemoryOffset; import sig.modules.RabiRibi.MemoryType; import sig.modules.RabiRibi.Overlay; +import sig.modules.RabiRibi.RabiUtils; +import sig.modules.RabiRibi.SmoothObject; import sig.modules.utils.PsapiTools; import sig.utils.DrawUtils; import sig.utils.FileUtils; @@ -36,9 +46,27 @@ public class RabiRibiModule extends Module{ HashMap entities = new HashMap(); final static int MAX_ENTITIES_TO_UPDATE = 500; final static int ENTITY_ARRAY_ELEMENT_SIZE = 0x704; + public HashMap lookup_table = new HashMap(); + int mapx = 0, mapy = 0; + public String statustext = ""; + public int statustime = 0; + public int moneyearned = 0; + public int moneytime = 0; + public int lastmoney = -1; public Overlay overlay; + public SmoothObject en_counter = new SmoothObject(0,0,0,0,this){ + public void draw(Graphics g) { + int playtime = readIntFromMemory(MemoryOffset.PLAYTIME); + if (moneyearned>0 && moneytime>playtime) { + setTarget(overlay.getScreenPosition(readFloatFromErinaData(MemoryOffset.ERINA_XPOS), readFloatFromErinaData(MemoryOffset.ERINA_YPOS))); + //System.out.println(x+","+y); + DrawUtils.drawCenteredOutlineText(g, sigIRC.panel.rabiRibiMoneyDisplayFont, (int)x, (int)y-96, 2, Color.ORANGE, Color.BLACK, "+"+moneyearned+"EN"); + } + } + }; + public RabiRibiModule(Rectangle2D bounds, String moduleName) { super(bounds, moduleName); //Initialize(); @@ -48,6 +76,9 @@ public class RabiRibiModule extends Module{ } private void Initialize() { + + RabiUtils.module = this; + List pids; try { pids = PsapiTools.getInstance().enumProcesses(); @@ -82,6 +113,15 @@ public class RabiRibiModule extends Module{ } this.overlay = new Overlay(this); + + EntityLookupData.parent=this; + EntityLookupData.loadEntityLookupData(lookup_table); + + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleWithFixedDelay(()->{ + UpdateEntities(); + //System.out.println("Called Entity creation "+callcount+" times."); + }, 1000, 1000, TimeUnit.MILLISECONDS); } public void ApplyConfigWindowProperties() { @@ -95,6 +135,25 @@ public class RabiRibiModule extends Module{ super.run(); updateEntities(); overlay.run(); + + if (lastmoney==-1) { + lastmoney = readIntFromMemory(MemoryOffset.MONEY); + } else + { + int currentmoney = readIntFromMemory(MemoryOffset.MONEY); + if (currentmoney>lastmoney) { + if (moneyearned==0) { + en_counter.setPosition(overlay.getScreenPosition(readFloatFromErinaData(MemoryOffset.ERINA_XPOS), readFloatFromErinaData(MemoryOffset.ERINA_YPOS))); + } + moneyearned+=currentmoney-lastmoney; + moneytime = readIntFromMemory(MemoryOffset.PLAYTIME)+60; + } + lastmoney = currentmoney; + } + if (moneyearned>0 && moneytime getEntities() { @@ -105,24 +164,47 @@ public class RabiRibiModule extends Module{ //System.out.println("Size is "+size); List idsToRemove = new ArrayList(); - for (Integer i : entities.keySet()) { - Entity ent = entities.get(i); - if (!ent.run()) { - idsToRemove.add(i); + try { + for (Integer i : entities.keySet()) { + Entity ent = entities.get(i); + if (!ent.run()) { + idsToRemove.add(i); + } } - } - for (Integer i : idsToRemove) { - if (!overlay.objects.remove(entities.get(i).marker)) { - System.out.println("WARNING! Could not remove overlay EntityMarker. Possible memory leak occurring!"); + for (Integer i : idsToRemove) { + if (!overlay.objects.remove(entities.get(i).marker)) { + System.out.println("WARNING! Could not remove overlay EntityMarker. Possible memory leak occurring!"); + } + entities.remove(i); + //System.out.println("Removed entity "+i+". Entities remaining: "+entities.size()); } - entities.remove(i); + } catch (ConcurrentModificationException e) { + } //System.out.println("Starting address is 0x"+Long.toHexString(readIntFromMemory(MemoryOffset.ENTITY_ARRAY))); - long arrayPtr = readIntFromMemory(MemoryOffset.ENTITY_ARRAY); //System.out.println("Array Pointer starts at 0x"+Long.toHexString(arrayPtr)); + int currentx = (int)(readFloatFromErinaData(MemoryOffset.ERINA_XPOS)/1280); + int currenty = (int)(readFloatFromErinaData(MemoryOffset.ERINA_YPOS)/720); + if (mapx!=(int)(readFloatFromErinaData(MemoryOffset.ERINA_XPOS)/1280) || + mapy!=(int)(readFloatFromErinaData(MemoryOffset.ERINA_YPOS)/720)) { + //System.out.println("Update Entities."); + mapx=currentx; + mapy=currenty; + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.schedule(()->{ + UpdateEntities(); + //System.out.println("Called Entity creation "+callcount+" times."); + }, 200, TimeUnit.MILLISECONDS); + } + } + + private void UpdateEntities() { + int callcount=0; + long arrayPtr = readIntFromMemory(MemoryOffset.ENTITY_ARRAY); for (int i=0;i0.5f) { - g.setColor(Color.RED); - } - DrawUtils.drawOutlineText(g, sigIRC.panel.programFont, position.getX(), position.getY()+(i+=24), 3, g.getColor(), Color.WHITE, "XSPD "+readFloatFromErinaData(MemoryOffset.ERINA_XSPEED)); - g.setColor(Color.BLACK); /*DrawUtils.drawOutlineText(g, sigIRC.panel.programFont, position.getX(), position.getY()+(i+=24), 3, Color.BLACK, Color.WHITE, "Sunny Beam: "+Arrays.toString( new int[]{ @@ -169,21 +246,40 @@ public class RabiRibiModule extends Module{ DrawUtils.drawOutlineText(g, sigIRC.panel.programFont, position.getX()+20, position.getY()+(i+=24), 3, Color.BLACK, Color.WHITE, s); }*/ - for (Integer numb : entities.keySet()) { - Entity ent = entities.get(numb); - if (ent.getLastHitTime()>readIntFromMemory(MemoryOffset.PLAYTIME)-180) { - for (String s : TextUtils.WrapText("Entity "+ent.getID()+": "+ent.getHealth()+"/"+ent.getMaxHealth()+" HP "+((ent.getHealth()/(float)ent.getMaxHealth())*100)+"%".replaceAll(",", ", "), sigIRC.panel.programFont, position.getWidth()-20)) { - DrawUtils.drawOutlineText(g, sigIRC.panel.programFont, position.getX()+20, position.getY()+(i+=24), 3, Color.BLACK, Color.WHITE, - s); + try { + for (Integer numb : entities.keySet()) { + Entity ent = entities.get(numb); + if (ent.getLastHitTime()>readIntFromMemory(MemoryOffset.PLAYTIME)-180) { + for (String s : TextUtils.WrapText("Entity "+ent.getID()+": "+ent.getHealth()+"/"+ent.getMaxHealth()+" HP "+((ent.getHealth()/(float)ent.getMaxHealth())*100)+"%".replaceAll(",", ", "), sigIRC.panel.programFont, position.getWidth()-20)) { + DrawUtils.drawOutlineText(g, sigIRC.panel.programFont, position.getX()+20, position.getY()+(i+=24), 3, Color.BLACK, Color.WHITE, + s); + } } } + } catch (ConcurrentModificationException e) { + } + + i+=24; + + int playtime = readIntFromMemory(MemoryOffset.PLAYTIME); + + if (statustext.length()>0 && statustime>playtime-300) { + DrawUtils.drawOutlineText(g, sigIRC.panel.programFont, position.getX(), position.getY()+(i+=48), 3, Color.GREEN, Color.LIGHT_GRAY, statustext); + } + + en_counter.draw(g); } } + + public void setStatusMessage(String msg) { + statustime = readIntFromMemory(MemoryOffset.PLAYTIME); + statustext = msg; + } public void keypressed(KeyEvent ev) { super.keypressed(ev); - /*if (ev.getKeyCode()==KeyEvent.VK_HOME) { + if (ev.getKeyCode()==KeyEvent.VK_HOME) { String memFile = sigIRC.BASEDIR+"memoryDump.txt"; FileUtils.logToFile("Memory Dump of All Loaded Entities:", memFile); for (Integer numb : entities.keySet()) { @@ -194,8 +290,8 @@ public class RabiRibiModule extends Module{ FileUtils.logToFile(" +"+Integer.toHexString(i*4)+": "+readDirectIntFromMemoryLocation(ptrArray)+" / "+readDirectFloatFromMemoryLocation(ptrArray)+"f", memFile); } } - } else - if (ev.getKeyCode()==KeyEvent.VK_END) { + } /*else + if (ev.getKeyCode()==KeyEvent.VK_END) { String memFile = sigIRC.BASEDIR+"memoryDump.txt"; FileUtils.logToFile("Memory Dump of All Erina Values:", memFile); for (int i=0;i200) { g2.setColor(shadow_color); - g2.drawString(as.getIterator(),(int)x+outline_size,(int)y+outline_size); + g2.drawString(as.getIterator(),(int)(x+outline_size+xoffset),(int)(y+outline_size+yoffset)); } else { FontRenderContext frc = g2.getFontMetrics(font).getFontRenderContext(); GlyphVector gv = font.createGlyphVector(frc, message); Rectangle2D box = gv.getVisualBounds(); - Shape shape = gv.getOutline((int)x,(int)y); + Shape shape = gv.getOutline((int)(x+xoffset),(int)(y+yoffset)); g2.setClip(shape); - g2.drawString(as.getIterator(),(int)x,(int)y); + g2.drawString(as.getIterator(),(int)(x+xoffset),(int)(y+yoffset)); g2.setClip(null); g2.setStroke(new BasicStroke(outline_size*2)); g2.setColor(shadow_color); @@ -40,7 +43,11 @@ public class DrawUtils { g2.draw(shape); } g2.setColor(text_color); - g2.drawString(as.getIterator(),(int)x,(int)y); + g2.drawString(as.getIterator(),(int)(x+xoffset),(int)(y+yoffset)); + } + public static void drawCenteredOutlineText(Graphics g, Font font, double x, double y, int outline_size, Color text_color, Color shadow_color, String message) { + Rectangle2D textBounds = TextUtils.calculateStringBoundsFont(message, font); + drawOutlineText(g,font,x,y,-textBounds.getWidth()/2,-textBounds.getHeight()/2,outline_size,text_color,shadow_color,message); } public static void drawText(Graphics g, double x, double y, Color color, String message) { if (message.length()>0) {