let’s see, you can have a tree, a station, a house, OR roads on a tile, but not 2+ of any one them, right?
can I have an example map? (the png file)
let’s see, you can have a tree, a station, a house, OR roads on a tile, but not 2+ of any one them, right?
can I have an example map? (the png file)
It’s possible to have two things on the same tile (bridges) but for that reason I chose not to display bridges on the map. At the moment I’m not displaying trees (considering to change that but then you won’t be able to see the height differences as well anymore). Any help will be heavily appreciated I’m at home with fever and a cold at the moment so I’m not really active behind the computer (sleeeeeep!), but I’m trying to go through the forums now and then to see if anything is posted
The png can be found at: http://stateofprofit.com/map/map.png
(updated every 20 minutes with what people built/altered land)
I removed the depth differences in the water which saved almost 10% on the file size (now pretty much exactly 1mb).
Yellow dots are farms, light red looking dots are stations.
Did you come up with any great idea?
For the people who logged in but didn’t figure out how the game works the missis made her first youtube video! The sound is a bit soft, she’ll try to keep the microphone closer next time
http://youtube.com/v/ra5IUIbeeLg
Mike
I only took the information down. I haven’t actually started writing anything. Not enough free time :\
this weekend I’ll look into it
EDIT: oh, and how can you tell the height of a tile with road/building on it. the grass tiles have different shades to indicate height, but how do you know what height level a building/road is on?
You don’t…
Mike
I’ve been playing a little today. It’s very addictive
Dunno if it’s been said already but the one thing that bugged me is when you’re building a road, and you have alternate between the hand and the road. Is there another way to do that ?
edit : I also had a problem with the music. It wouldn’t go through the headset and I couldn’t lower it either (even with the OS sound level)
ps : i’m on ubuntu 11.04
Wha? don’t you load the map from the image (tiles + their height?)
Thanks, it’s not too hard to make games but it takes either luck or lots of work (or a combination of the two) to make addictive ones
If you have the road piece selected you can scroll around the map using the right mouse button (or the arrow keys or wasd). It’s a good point though, the right mouse button trick should be documented somewhere as it can be seen as annoying :). The video above is doing it at about 3:03.
I have no idea about that one… I’m using jOrbis 0.0.17 with EasyOgg to play the ogg files, did anyone else encounter something similar, and if so, know a solution?
Nope ;). The map doesn’t contain all the information of the tiles, for example owner, station names, station directions and a few more things and it is only updated once every 20 minutes. For that reason (and that I don’t want to create custom code for the first time the game loads compared to streaming land information while playing) I’m not using the map for height+content.
I could maybe lower the initial loading time with like half a sec or so if I did, but I don’t think it’s worth it at this time.
Kind regards,
Mike
where are the heights stored? how big is all that data?
the map is dynamically generated based on what object is on the tile and the height of the tile right? how does the client get the height of the map? that’s sent later (as separate parts?)
so the tiles are loaded all at the beginning and heights are sent later or what?
and is the problem you want solved the fact that 3200x3200 tiles (alone, no height or any other data) is 1mb?
I like the new farms. However, there are some issues I’m having:
I don’t think it makes much sense to restrict building farms to that extreme. Building 1 farm will get you nowhere I haven’t got my first research-point yet, and have no idea how fast my 25% of income, will yield some points, but it feels like it is nerfed.
How do warehouses work? I have 950+ food in a warehouse and the inhabitants of the village are starving.
I see 100+K towns, they are starving, yet continue to grow fast. I think starvation should have a bigger impact on city growth.
Maybe it is not even implemented yet, so I’ll stop right here
The heights are originally stored in a mysql table and read in during the startup of the server. The heights are then stored as an int as part of the LandTile object. I’m not sure how big the data is as the table also contains owner and content but there are about 10 million squares at the moment and height is a number between 0 and 15.
The map is indeed dynamically generated on a schedule and stored as a png. The client gets the map from the png I posted before. The part that’s sent at startup and later is the game world and it’s sent as a gzipped string explaining everything necessary for the client to create each 16*16 container of land tiles.
The map and the game world are two different things
The tiles (including height and content and owner and special things like station information) is loaded when needed. When you scroll around in the game the tiles that are far away gets unloaded from the client and the ones that you scroll towards get loaded in. Doing it that way the world size is only limited by the server capacity (and at the moment, the size of the png map that gets downloaded).
The problem I have at the moment is that the 3200x3200px map png is 1mb which takes a long time to download. I’d very much like to figure out how to limit the size of that download (I’m fine with downloading the data in another way than a png if that’d help). I’m starting to think though that Riven is right (isn’t he always? :P) and it needs to be a combination of streaming and mipmapping.
Hi Riven, thanks a lot for testing again, I was missing you
Farms give you a lot of income for a beginning account, which is why you only get one for free There will be ways to get more farms later, for example through research and bidding. 1 research points is 100.000, so if you make 30.000 per game day and you put it all into research it’ll take you a few hours to get a point. In the beginning it’s usually better to expand instead of researching though to my experience.
The “starving” text is updated once a day (game time) so it might be that it just didn’t update yet. On the other hand there are some problems with how the city classifies starvation at the moment. The formula didn’t work out quite as Lindy (the missis) had anticipated with her excel sheets
Yep, they grow too quickly, see above.
Lindy wrote a post on the SoP forum regarding the farms and how they’re not really working like they should, you can find it here: http://stateofprofit.com/forum/index.php/ topic,62.15.html#msg236 (without the space between / and topic, riven broke all url’s to other smf forums :P). She’d love to discuss the formulas with you if you’d create an account on the forum
As a bonus information, I’m almost finished with a chat system so you can talk to the people currently online in the game
Kind regards,
Mike
so let me get this straight - all you want is a way to get an image of the map to the client with it being smaller than 1 mb? (if so, a binary solution is probably better and then you parse it client side to construct the image)
if so, i have enough time tonight to write you something, but i have homework for now
do you have code to show how your map is generated server side (the grabbing of heights and also getting what buildings are on tiles and then how to determine each pixel color and finally export all of the data to an image?)
Yes! If it’s possible that’d be awesome
Here’s the code:
public static void createMap() {
Log.info("Creating map");
final int size = Containers.landSize;
final Point2DInteger tempPoint = new Point2DInteger(0, 0);
// Create a buffered image on which to draw
final BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
//Get the direct access data for the image
final int[] rgb = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
LandTile landTile;
//Fill in the colors
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
tempPoint.setLocation(i - size / 2, j - size / 2);
landTile = Containers.getLandTile(tempPoint);
if (landTile.getHeight() < 5) {
rgb[(size - j - 1) * size + i] = 0 << 16 | 0 << 8 | 200;
} else {
switch (landTile.getContent()) {
case 5:
case 6:
case 7:
rgb[(size - j - 1) * size + i] = 130 << 16 | 130 << 8 | 130;
break;
case 8:
rgb[(size - j - 1) * size + i] = 170 << 16 | 170 << 8 | 170;
break;
case 9:
rgb[(size - j - 1) * size + i] = 255 << 16 | 160 << 8 | 120;
break;
case 10:
rgb[(size - j - 1) * size + i] = 255 << 16 | 255 << 8 | 170;
break;
case 11:
case 12:
case 13:
rgb[(size - j - 1) * size + i] = 120 << 16 | 120 << 8 | 0;
break;
case 14:
rgb[(size - j - 1) * size + i] = 240 << 16 | 0 << 8 | 220;
break;
default:
rgb[(size - j - 1) * size + i] =
(int)(99 * (landTile.getHeight() / 2f - 2f) / 12.5f + 41) << 16
| (int)(130 * (landTile.getHeight() / 2f - 2f) / 12.5f + 115) << 8
| (int)(99 * (landTile.getHeight() / 2f - 2f) / 12.5f + 41);
}
}
}
}
//Save as a temporary file
final File tempfile = new File(PropertyRetriever.getProperty("newImage") + ".tmp");
try {
if (!tempfile.exists()) {
tempfile.createNewFile();
}
ImageIO.write(img, "png", tempfile);
} catch (final IOException e) {
Log.error("Writing imagefile", e);
}
//Remove the old file
final File file = new File(PropertyRetriever.getProperty("newImage"));
int i = 0;
if (file.exists()) {
while (!file.delete() && i < 600) {
i++;
try {
Thread.sleep(100);
} catch (final InterruptedException e) {
Log.warning("Sleep", e);
}
}
if (i == 600) {
Log.error("Failed to remove old map image");
tempfile.delete();
return;
}
}
//Rename temp file
i = 0;
while (!tempfile.renameTo(new File(PropertyRetriever.getProperty("newImage"))) && i < 6000) {
i++;
try {
Thread.sleep(10);
} catch (final InterruptedException e) {
Log.warning("Sleep", e);
}
}
Log.info("Finished creating map");
}
Mike
Welp, I’ve only managed to bring the size down to a tiny bit under 75% (783,090 bytes) but also got heights included in the format so that should count for something.
It’s messy but I’m tired and don’t feel like working on it anymore for now. what it does is load your map “map.png” and then save it to my compressed format, with heights included. it then outputs an image “out.png” to show how the format can be used to make an image.
kinda slow, probably will optimize later. of course there won’t be the same initial ‘getting data delay’, since you get your data directly from a database instead of parsing an image to see what colors represent what.
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.imageio.ImageIO;
public class MapCompressor {
public static final int WIDTH = 3200;
public static final int HEIGHT = 3200;
public static void main(String[] args) throws IOException {
new MapCompressor();
}
public int rel(int cur, int last) {
if (cur == last)
return 0;
return cur > last ? 1 : 2;
}
public static int HEIGHT_CAPACITY = 4;
public static int OBJECT_CAPACITY = 2;
public static int SCAN_CAPACITY = 2;
public MapCompressor() throws IOException {
tiles = new byte[1 + WIDTH * HEIGHT / OBJECT_CAPACITY];
tileHeights = new byte[1 + WIDTH * HEIGHT / HEIGHT_CAPACITY];
scanLine = new byte[WIDTH / SCAN_CAPACITY];
BufferedImage image = ImageIO.read(new File("map.png"));
int[] pixels = new int[3200 * 3200];
image.getRGB(0, 0, 3200, 3200, pixels, 0, 3200);
for (int x = 0; x < WIDTH; x++) {
int rgb = pixels[x];
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
int h = 0;
if (b != 200) {
g -= 115;
g /= 5;
h = g;
}
scanLine[x / SCAN_CAPACITY] |= h << ((x % 2) << 2);
}
for (int x = 0; x < WIDTH; x++) {
int height = (scanLine[x / SCAN_CAPACITY] >> ((x % 2) << 2)) & 0xF;
for (int y = 0; y < HEIGHT; y++) {
int rgb = pixels[y * WIDTH + x];
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
if (r == 130 && g == 130 && b == 130) {
setObject(x, y, 1); // road
} else if (r == 190 && g == 190 && b == 190) {
setObject(x, y, 2); // house
} else if (r == 255 && g == 170 && b == 170) {
setObject(x, y, 3); // station
} else if (r == 255 && g == 255 && b == 170) {
setObject(x, y, 4); // farm
} else {
int h = 0;
if (b != 200) {
g -= 115;
g /= 5;
h = g;
}
int rel = rel(h, height);
setHeight(x, y, rel);
if (rel == 2)
rel = -1;
height += rel;
}
}
}
byte[] compressed = compress();
System.out.println("Compressed size: " + compressed.length);
File file = new File("out.png");
System.out.println("Outputting image to: " + file.getAbsolutePath());
writeImage(compressed, file);
System.out.println("Done.");
}
public void writeImage(byte[] data, File file) throws IOException {
GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(data));
byte[] block = new byte[8192];
Buffer buffer = new Buffer();
int len;
while ((len = in.read(block)) != -1) {
buffer.put(block, 0, len);
}
in.close();
int w = buffer.getShort();
int h = buffer.getShort();
System.out.println(w + " " + h);
tiles = new byte[1 + w * h / OBJECT_CAPACITY]; // constant for client and server
tileHeights = new byte[1 + w * h / HEIGHT_CAPACITY]; // constant for client and server
scanLine = new byte[w / SCAN_CAPACITY];
for (int i = 0; i < scanLine.length; i++) {
scanLine[i] = (byte) buffer.get();
}
for (int i = 0; i < tiles.length; i++) {
tiles[i] = (byte) buffer.get();
}
for (int i = 0; i < tileHeights.length; i++) {
tileHeights[i] = (byte) buffer.get();
}
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < WIDTH; x++) {
int height = (scanLine[x / SCAN_CAPACITY] >> ((x % 2) << 2)) & 0xF;
for (int y = 0; y < HEIGHT; y++) {
int obj = (int) getObject(x, y);
int color = 0;
int rel = (int) getHeight(x, y);
if (rel == 2)
rel = -1;
height += rel;
switch (obj) {
case 0:
if (height == 0) {
color = 0x0000C8;
} else {
color = (int)(99 * (height / 2f - 2f) / 12.5f + 41) << 16
| (int)(130 * (height / 2f - 2f) / 12.5f + 115) << 8
| (int)(99 * (height / 2f - 2f) / 12.5f + 41);
}
break;
case 1:
color = 0x828282;
break;
case 2:
color = 0xBEBEBE;
break;
case 3:
color = 0xFFAAAA;
break;
case 4:
color = 0xFFFFAA;
break;
}
image.setRGB(x, y, color);
}
}
ImageIO.write(image, "png", file);
}
private byte[] tiles;
private byte[] tileHeights;
private byte[] scanLine;
public int off(int x, int y, int capacity, int bitcount) {
return ((y * WIDTH + x) % capacity) * bitcount;
}
public int get(int x, int y, int size) {
return (y * WIDTH + x) / size;
}
public int getObject(int x, int y) {
return (tiles[get(x, y, OBJECT_CAPACITY)] >> off(x, y, OBJECT_CAPACITY, 3)) & 0x7;
}
public void setObject(int x, int y, int val) {
tiles[get(x, y, OBJECT_CAPACITY)] |= (val & 0x7) << off(x, y, OBJECT_CAPACITY, 3);
}
public void setHeight(int x, int y, int val) {
tileHeights[get(x, y, HEIGHT_CAPACITY)] |= (val & 0x3) << off(x, y, HEIGHT_CAPACITY, 2);
}
public int getHeight(int x, int y) {
return (tileHeights[get(x, y, HEIGHT_CAPACITY)] >> off(x, y, HEIGHT_CAPACITY, 2)) & 0x3;
}
public byte[] compress() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream comp = new GZIPOutputStream(out);
comp.write(WIDTH >> 8);
comp.write(WIDTH);
comp.write(HEIGHT >> 8);
comp.write(HEIGHT);
comp.write(scanLine);
comp.write(tiles);
comp.write(tileHeights);
comp.close();
return out.toByteArray();
}
}
25% is still 25% I’ll have a look on the server side so it doesn’t cost a minute of cpu time to do seeing as it needs to be done every 20 or so minutes
True, I’ll have a look at the performance and the code
Thanks a lot! Here you go with a medal
In other news, the game now has a chat function (making my own input box in graphics 2D that listens to the lwjgl mouse and keyboard wasn’t the easiest thing I’ve ever done :)) and the missis reworked all the formulas around farms, hopefully they feel better now Riven
Mike
Loosing and loosing money :’(. All my buses and truck get money (more than 500) and I loose every thing with station maintenance :-.
There is 2 strange buses :
Hi Bonbon,
By building stations in big cities you’ll get a lot of passengers, you should try to keep your stations more or less empty before expanding as station maintenance is a significant part (the game has become a bit more unforgiving since last round :))
Lindy saw that the roads in your part of the world wasn’t really optimal so she built some more for you
She’s looking into what needs to change to even out the new experience a bit more without making us all have 100.000.000 after two weeks
Regarding buses not showing their correct position, very possible, I have it on the bug list on the forum, mostly refreshing the page solves the issues though so I didn’t make it higher priority than adding new things. The bug list is growing though so I’ll soon have to halt new things and get rid of some bugs again.
Mike
This is fixed thanks to the latest version of LWJGL, thank you for reporting it
I used some of your textures and created some new ones (at 1/16th the size of yours due to kb’s), this is what I’ve got so far, good enough until I get a graphics person The only thing I might add on my own is the ground around the buildings, it doesn’t look very good with grass…
This is a city of about half a million inhabitants:
http://stateofprofit.com/pictures/screenshots/city1.png
http://stateofprofit.com/pictures/screenshots/city2.png
Yay, more than 10.000 views!
Mike