shithub: city

Download patch

ref: 07469015a833ea56db430aed28f004e205549360
parent: ef9b582868430fefaf733b7dc13ed6e9dc303bac
author: qwx <qwx@sciops.net>
date: Sat Nov 26 17:53:05 EST 2022

implement menus + build menu

big commit; a few things left to fix

--- /dev/null
+++ b/build.c
@@ -1,0 +1,84 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include "dat.h"
+#include "fns.h"
+
+extern Mousectl *mc;
+
+static Tile *site;
+
+int
+couldbuild(Building *b)
+{
+	int i;
+
+	for(i=0; i<nelem(b->buildcost); i++)
+		if(b->buildcost[i] > stock[i])
+			return 0;
+	return 1;
+}
+
+int
+isbuildable(Tile *t)
+{
+	return t->b == nil && t->t == terrains + Tplain;
+}
+
+static char *
+genmenu(int n)
+{
+	switch(n){
+	case 0: return "build";
+	default: return nil;
+	}
+}
+
+static Menu menu = {0, genmenu};
+
+static int
+select(Tile *t)
+{
+	Building *b;
+
+	b = buildings + (t - map);
+	return b >= buildings + nelem(buildings) ? -1 : 0;
+}
+
+static int
+action(Tile *t)
+{
+	int i;
+	Building *b;
+
+	b = buildings + (t - map);
+	if(b >= buildings + nelem(buildings))
+		return -1;
+	selected = t;
+	if(menuhit(3, mc, &menu, nil) < 0)
+		return -1;
+	/* FIXME: show some error message in status bar */
+	if(!couldbuild(b))
+		return 0;
+	for(i=0; i<nelem(b->buildcost); i++){
+		stock[i] -= b->buildcost[i];
+		assert(stock[i] >= 0);
+	}
+	spawn(site, b - buildings);
+	deselect();
+	return 0;
+}
+
+Menuptr
+buildmenu(Tile *t)
+{
+	site = t;
+	selected = nil;
+	gsetcursor(Curstarget);
+	mapdrawfn = drawbuildmenu;
+	selectfn = select;
+	return action;
+}
--- a/city.c
+++ b/city.c
@@ -7,9 +7,10 @@
 #include "dat.h"
 #include "fns.h"
 
+Mousectl *mc;
+Keyboardctl *kc;
+
 static int tdiv;
-static Keyboardctl *kc;
-static Mousectl *mc;
 static Channel *tmc;
 
 void *
@@ -48,8 +49,10 @@
 void
 threadmain(int argc, char **argv)
 {
+	uint b, bo;
 	Rune r;
 	Mouse mo;
+	Point p;
 
 	ARGBEGIN{
 	}ARGEND
@@ -66,7 +69,9 @@
 	readfs();
 	resetdraw();
 	startsim();
-	mo.xy = ZP;
+	mo.xy = EP;
+	memset(&mo, 0, sizeof mo);
+	bo = 0;
 	enum{
 		Aresize,
 		Amouse,
@@ -88,11 +93,31 @@
 			mo = mc->Mouse;
 			resetdraw();
 			break;
+		/* FIXME: clean up */
 		case Amouse:
-			if(eqpt(mo.xy, ZP))
+			if(eqpt(mo.xy, EP))
 				mo = mc->Mouse;
-			if(mc->buttons & 1<<0)
-				mouseselect(mc->xy);
+			b = mc->buttons;
+			p = s2p(mc->xy);
+			if(eqpt(p, EP) || b == 0){
+				//if(b != 0)
+				//	deselect();
+				mo = mc->Mouse;
+				bo = b;
+				break;
+			}
+			if((b & (1<<0|1<<2)) != 0
+			&& (b & (1<<0|1<<2) != (bo & (1<<0|1<<2)))){
+				mouseselect(p);
+				updatedraw(1);
+			}
+			if((mc->buttons & 1<<2) != 0
+			&& (b & 1<<2) != (bo & 1<<2)){
+				actionmenu(p);
+				updatedraw(1);
+			}
+			mo = mc->Mouse;
+			bo = mo.buttons;
 			break;
 		case Akbd:
 			switch(r){
@@ -115,7 +140,7 @@
 			}
 			break;
 		case Aanim:
-			updatedraw();
+			updatedraw(0);
 			break;
 		}
 	}
--- a/dat.h
+++ b/dat.h
@@ -126,6 +126,21 @@
 };
 extern Tile *map;
 extern int mapwidth, mapheight;
+extern Tile *selected;
+
+extern int stock[Gtot], rstock[Rtot];
+
+typedef int (*Menuptr)(Tile*);
+enum{
+	Cursdef,
+	Curstarget,
+};
+extern void	(*mapdrawfn)(void);
+extern int	(*selectfn)(Tile*);
+extern int repaint;
+extern Menuptr menufn;
+
+extern Point EP;
 
 enum{
 	Te9 = 1000000000,
--- /dev/null
+++ b/data.c
@@ -1,0 +1,19 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <cursor.h>
+#include <mouse.h>
+#include "dat.h"
+#include "fns.h"
+
+Cursor targetcursor = {
+	{-7, -7},
+	{0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
+	 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
+	 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
+	 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8, },
+	{0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
+	 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
+	 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
+	 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00, }
+};
--- a/defs.c
+++ b/defs.c
@@ -1,7 +1,9 @@
 #include <u.h>
 #include <libc.h>
 #include <draw.h>
+#include <mouse.h>
 #include "dat.h"
+#include "fns.h"
 
 Good goods[] = {
 	[Gfish]{
--- a/drw.c
+++ b/drw.c
@@ -4,12 +4,14 @@
 #include "dat.h"
 #include "fns.h"
 
+Point EP = {-1,-1};
+
 int scale = 1;
 Point pan;
+void (*mapdrawfn)(void);
 QLock drwlock;
+int repaint;
 
-static Tile *selected;
-
 enum{
 	Cbg,
 	Ctext,
@@ -17,11 +19,11 @@
 };
 static Image *cols[Cend];
 
-static Rectangle fbr, scrwin, hudr;
+Rectangle scrwin;
+static Rectangle fbr, hudr;
 static Point scrofs, tlwin, fbbufr;
 static Image *fb;
 static u32int *fbbuf;
-static int doupdate;
 
 static Image *
 eallocimage(Rectangle r, ulong chan, int repl, ulong col)
@@ -33,6 +35,30 @@
 	return i;
 }
 
+Point
+s2p(Point p)
+{
+	if(!ptinrect(p, scrwin))
+		return EP;
+	return divpt(subpt(p, scrwin.min), scale);
+}
+
+Tile *
+p2t(Point p)
+{
+	p = divpt(p, Tilesz);
+	assert(p.x >= 0 && p.x < mapwidth && p.y >= 0 && p.y < mapheight);
+	return map + p.y * mapwidth + p.x;
+}
+
+Tile *
+s2t(Point p)
+{
+	if(eqpt(p, EP))
+		return nil;
+	return p2t(p);
+}
+
 static int
 clippic(Point pp, Pic *pic, Point *scpp, Point *ofs, Point *sz)
 {
@@ -105,32 +131,32 @@
 	}
 }
 
-static Point
-tile2xy(Tile *m)
+void
+composeat(Tile *m, u32int c)
 {
-	Point p;
+	int k, n, x, w;
+	u32int v, *pp;
 
-	p.x = ((m - map) % mapwidth) * Tilesz;
-	p.y = ((m - map) / mapwidth) * Tilesz;
-	return p;
+	w = fbr.max.x * fbr.max.y / scale;
+	pp = fbbuf + (m-map) / mapwidth * w
+		+ (m-map) % mapwidth * Tilesz * scale;
+	n = Tilesz;
+	w = (fbr.max.x / scale - Tilesz) * scale;
+	while(n-- > 0){
+		x = Tilesz;
+		while(x-- > 0){
+			v = *pp;
+			v = (v & 0xff0000) + (c & 0xff0000) >> 1 & 0xff0000
+				| (v & 0xff00) + (c & 0xff00) >> 1 & 0xff00
+				| (v & 0xff) + (c & 0xff) >> 1 & 0xff;
+			k = scale;
+			while(k-- > 0)
+				*pp++ = v;
+		}
+		pp += w;
+	}
 }
 
-static Point
-scr2tilexy(Point p)
-{
-	p = divpt(subpt(p, scrwin.min), scale * Tilesz);
-	assert(p.x < tlwin.x && p.x >= 0 && p.y < tlwin.y && p.y >= 0);
-	return p;
-}
-
-static Tile *
-scr2tile(Point p)
-{
-	assert(ptinrect(p, scrwin));
-	p = scr2tilexy(p);
-	return map + p.y * mapwidth + p.x;
-}
-
 static void
 drawhud(void)
 {
@@ -144,6 +170,42 @@
 }
 
 static void
+drawmenuselected(void)
+{
+	int i, n;
+	char s[256], *sp;
+	Point p;
+	Building *b;
+
+	if(selected == nil)
+		return;
+	assert(selected >= map && selected < map + mapwidth * mapheight);
+	b = buildings + (selected - map);
+	if(b >= buildings + nelem(buildings)){
+		fprint(2, "nope\n");
+		return;
+	}
+	p = addpt(hudr.min, Pt(0, font->height));
+	sp = s;
+	sp = seprint(sp, s+sizeof s, "%s time %d cost ",
+		b->name, b->buildtime);
+	for(i=0, n=0; i<nelem(b->product); i++)
+		if(b->buildcost[i] > 0){
+			sp = seprint(sp, s+sizeof s, "%s%d %s",
+				n > 0 ? "," : "", b->buildcost[i], goods[i].name);
+			n++;
+		}
+	sp = seprint(sp, s+sizeof s, " product ");
+	for(i=0, n=0; i<nelem(b->product); i++)
+		if(b->product[i] > 0){
+			sp = seprint(sp, s+sizeof s, "%s%d %s",
+				n > 0 ? "," : "", b->product[i], goods[i].name);
+			n++;
+		}
+	string(screen, p, cols[Ctext], ZP, font, s);
+}
+
+static void
 drawtile(Tile *m)
 {
 	Pic *pic;
@@ -153,62 +215,85 @@
 }
 
 void
-updatedraw(void)
+drawbuildings(void)
 {
 	int x, y;
 	Tile *m;
+	Building *b;
 
-	qlock(&drwlock);
+	for(y=0, m=map, b=buildings; y<tlwin.y; y++){
+		for(x=0; x<tlwin.x; x++, m++, b++){
+			if(b >= buildings + nelem(buildings))
+				return;
+			drawpic(tile2xy(m), &b->Pic);
+			if(!couldbuild(b))
+				composeat(m, 0x555555);
+			if(m->stale){
+				m->stale = 0;
+				repaint = 1;
+			}
+		}
+		m += mapwidth - tlwin.x;
+	}
+}
+
+static void
+drawmap(void)
+{
+	int x, y;
+	Tile *m;
+
 	for(y=0, m=map; y<tlwin.y; y++){
 		for(x=0; x<tlwin.x; x++, m++)
 			if(m->stale){
 				drawtile(m);
 				m->stale = 0;
-				doupdate = 1;
+				repaint = 1;
 			}
 		m += mapwidth - tlwin.x;
 	}
-	qunlock(&drwlock);
-	if(!doupdate)
-		return;
-	flushfb();
-	doupdate = 0;
 }
 
 void
-mouseselect(Point p)
+drawbuildmenu(void)
 {
-	doupdate = 1;
-	if(!ptinrect(p, scrwin)){
-		selected = nil;
-		return;
-	}
-	selected = scr2tile(p);
-	doupdate = 1;
-	updatedraw();
+	drawbuildings();
+	drawmenuselected();
 }
 
 static void
-redraw(void)
+redrawcanvas(void)
 {
 	int x, y;
 	Tile *m;
 
-	draw(fb, fb->r, display->black, nil, ZP);
+	memset(fbbuf, 0, fbbufr.x * fbbufr.y * sizeof *fbbuf);
 	for(y=0, m=map; y<tlwin.y; y++){
 		for(x=0; x<tlwin.x; x++, m++)
 			m->stale = 1;
 		m += mapwidth - tlwin.x;
 	}
-	updatedraw();
 }
 
 void
+updatedraw(int all)
+{
+	qlock(&drwlock);
+	if(all || repaint)
+		redrawcanvas();
+	(mapdrawfn != nil ? mapdrawfn : drawmap)();
+	qunlock(&drwlock);
+	flushfb();
+	repaint = 0;
+}
+
+void
 flushfb(void)
 {
 	uchar *p;
 	Rectangle r, r2;
 
+	lockdisplay(display);
 	p = (uchar *)fbbuf;
 	if(scale == 1){
 		loadimage(fb, fb->r, p, fbbufr.x * fbbufr.y * sizeof *fbbuf);
@@ -225,6 +310,7 @@
 	}
 	drawhud();
 	flushimage(display, 1);
+	unlockdisplay(display);
 }
 
 void
@@ -261,8 +347,7 @@
 	fbbuf = emalloc(fbbufr.x * fbbufr.y * sizeof *fbbuf);
 	if(!eqpt(scrofs, ZP))
 		draw(screen, screen->r, cols[Cbg], nil, ZP);
-	doupdate = 1;
-	redraw();
+	updatedraw(1);
 }
 
 void
@@ -270,6 +355,8 @@
 {
 	if(initdraw(nil, nil, "city") < 0)
 		sysfatal("initdraw: %r");
+	display->locking = 1;
+	unlockdisplay(display);
 	cols[Cbg] = eallocimage(Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
 	cols[Ctext] = display->black;
 	fmtinstall('P', Pfmt);
--- a/fns.h
+++ b/fns.h
@@ -2,12 +2,24 @@
 void	init(void);
 void	initmap(void);
 int	mhdist(int, int, int, int);
+Point	tile2xy(Tile*);
+Point	s2p(Point);
+Tile*	p2t(Point);
+Tile*	s2t(Point);
 void	startsim(void);
+void	spawn(Tile*, int);
 void	readfs(void);
 void	initdrw(void);
 void	resetdraw(void);
-void	updatedraw(void);
+void	updatedraw(int);
 void	flushfb(void);
+void	drawbuildmenu(void);
 void	mouseselect(Point);
+void	actionmenu(Point);
+void	deselect(void);
+void	gsetcursor(int);
 int	min(int, int);
 int	max(int, int);
+int	couldbuild(Building*);
+Menuptr	buildmenu(Tile*);
+int	isbuildable(Tile*);
--- a/fs.c
+++ b/fs.c
@@ -53,8 +53,8 @@
 
 	for(t=terrains; t<terrains+nelem(terrains); t++)
 		loadpic(t->name, Tilesz, &t->Pic);
-	b = buildings + Btownhall;
-	loadpic(b->name, Tilesz, &b->Pic);
+	for(b=buildings; b<buildings+nelem(buildings); b++)
+		loadpic(b->name, Tilesz, &b->Pic);
 }
 
 void
--- a/map.c
+++ b/map.c
@@ -7,6 +7,16 @@
 int mapwidth, mapheight;
 Tile *map;
 
+Point
+tile2xy(Tile *m)
+{
+	Point p;
+
+	p.x = ((m - map) % mapwidth) * Tilesz;
+	p.y = ((m - map) / mapwidth) * Tilesz;
+	return p;
+}
+
 int
 mhdist(int x, int y, int x´, int y´)
 {
--- /dev/null
+++ b/menu.c
@@ -1,0 +1,111 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <cursor.h>
+#include "dat.h"
+#include "fns.h"
+
+extern Mousectl *mc;
+extern Cursor targetcursor;
+
+/* FIXME: safer: Menu struct with menu state: default, build, etc. */
+
+Tile *selected;
+int (*selectfn)(Tile *);
+Menuptr menufn;
+
+typedef struct Gmenu Gmenu;
+struct Gmenu{
+	char *name;
+	int (*checkfn)(Tile*);
+	Menuptr (*initfn)(Tile*);
+	int (*selectfn)(Tile*);
+};
+enum{
+	Gmenubuild,
+	Gmenuend,
+};
+static Gmenu actions[Gmenuend] = {
+	[Gmenubuild] {"build", isbuildable, buildmenu},
+};
+static Gmenu *favail[nelem(actions)];
+static int navail;
+
+extern Rectangle scrwin;
+
+void
+gsetcursor(int curs)
+{
+	Cursor *c;
+
+	switch(curs){
+	case Curstarget: c = &targetcursor; break;
+	case Cursdef: /* wet floor */
+	default: c = nil; break;
+	}
+	setcursor(mc, c);
+}
+
+static char *
+genmenu(int n)
+{
+	return n < navail ? favail[n]->name : nil;
+}
+
+static Menu menu = {0, genmenu};
+
+static Menuptr
+setmenu(Tile *t)
+{
+	int n;
+	Gmenu *f;
+
+	memset(favail, 0, sizeof favail);
+	navail = 0;
+	for(f=actions; f<actions+nelem(actions); f++)
+		if(f->checkfn == nil || f->checkfn(t))
+			favail[navail++] = f;
+	if(navail == 0)
+		return nil;
+	if((n = menuhit(3, mc, &menu, nil)) < 0)
+		return nil;
+	return favail[n]->initfn(t);
+}
+
+void
+actionmenu(Point p)
+{
+	Tile *t;
+
+	t = p2t(p);
+	assert(t != nil);
+	selected = t;
+	if(menufn == nil){
+		menufn = setmenu(t);
+		if(menufn == nil)
+			deselect();
+	}else if(menufn(t) < 0)
+		deselect();
+}
+
+void
+mouseselect(Point p)
+{
+	selected = p2t(p);
+	if(selectfn != nil && selectfn(selected) < 0)
+		deselect();
+}
+
+void
+deselect(void)
+{
+	selected = nil;
+	menufn = nil;
+	mapdrawfn = nil;
+	selectfn = nil;
+	gsetcursor(Cursdef);
+	repaint = 1;
+}
--- a/mkfile
+++ b/mkfile
@@ -2,11 +2,14 @@
 BIN=$home/bin/$objtype
 TARG=city
 OFILES=\
+	build.$O\
 	city.$O\
+	data.$O\
 	defs.$O\
 	drw.$O\
 	fs.$O\
 	map.$O\
+	menu.$O\
 	sim.$O\
 
 HFILES=dat.h fns.h
--- a/sim.c
+++ b/sim.c
@@ -10,21 +10,22 @@
 int paused;
 vlong clock;
 
+enum{
+	UpkeepΔt = 150,
+};
+int stock[Gtot], rstock[Rtot];
+
 static int tdiv;
 
 static Tile **objs, **objhead;
 static int maxobj;
 
-enum{
-	UpkeepΔt = 150,
-};
-static int stock[Gtot], rstock[Rtot];
-
-static void
+void
 spawn(Tile *m, int n)
 {
 	Tile **o;
 
+	assert(m != nil);
 	if(objhead - objs >= maxobj)
 		sysfatal("spawn: out of bounds");
 	m->t = terrains + Tplain;