diff --git a/NEWS b/NEWS index fbc40ce43..a150fa7f7 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,9 @@ DFHack future - craft-age-wear: make crafted items wear out with time like in old versions (bug 6003) - adamantine-cloth-wear: stop adamantine clothing from wearing out (bug 6481) + New plugins: + - rendermax: replace the renderer with something else. Most interesting is "rendermax light"- a lighting engine for df. + Misc improvements: - digfort: improved csv parsing, add start() comment handling - exterminate: allow specifying a caste (exterminate gob:male) diff --git a/Readme.rst b/Readme.rst index fc9478f5d..a5e038899 100644 --- a/Readme.rst +++ b/Readme.rst @@ -337,6 +337,27 @@ that pre-filled. .. image:: images/command-prompt.png +rendermax +--------- +A collection of renderer replacing/enhancing filters. For better effect try changing the +black color in palette to non totally black. For more info see thread in forums: +http://www.bay12forums.com/smf/index.php?topic=128487.0 + +Options: + + :rendermax trippy: Randomizes each tiles color. Used for fun mainly. + :rendermax light: Enable lighting engine. + :rendermax light reload: Reload the settings file. + :rendermax light sun |cycle: Set time to (in hours) or set it to df time cycle. + :rendermax occlusionON|occlusionOFF: Show debug occlusion info. + :rendermax disable: Disable any filter that is enabled. + +An image showing lava and dragon breath. Not pictured here: sunlight, shining items/plants, +materials that color the light etc... + +.. image:: images/rendermax.png + + Adventure mode ============== diff --git a/images/rendermax.png b/images/rendermax.png new file mode 100644 index 000000000..942f4cc28 Binary files /dev/null and b/images/rendermax.png differ diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index e13284274..650fe1e98 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -172,3 +172,7 @@ OPTION(BUILD_SKELETON "Build the skeleton plugin." OFF) if(BUILD_SKELETON) add_subdirectory(skeleton) endif() +OPTION(BUILD_RENDERMAX "Build the rendermax alt-renderers plugin." OFF) +if(BUILD_RENDERMAX) + add_subdirectory(rendermax) +endif() diff --git a/plugins/lua/rendermax.lua b/plugins/lua/rendermax.lua new file mode 100644 index 000000000..d44027c16 --- /dev/null +++ b/plugins/lua/rendermax.lua @@ -0,0 +1,4 @@ +local _ENV = mkmodule('plugins.rendermax') + + +return _ENV \ No newline at end of file diff --git a/plugins/rendermax/CMakeLists.txt b/plugins/rendermax/CMakeLists.txt new file mode 100644 index 000000000..ba589732d --- /dev/null +++ b/plugins/rendermax/CMakeLists.txt @@ -0,0 +1,41 @@ +PROJECT (rendermax) + +# A list of source files +SET(PROJECT_SRCS + rendermax.cpp + renderer_light.cpp +) +# A list of headers +SET(PROJECT_HDRS + renderer_opengl.hpp + renderer_light.hpp +) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) + +# mash them together (headers are marked as headers and nothing will try to compile them) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) + + +#linux +IF(UNIX) + add_definitions(-DLINUX_BUILD) + SET(PROJECT_LIBS + # add any extra linux libs here + lua + dfhack-tinythread + ${PROJECT_LIBS} + ) +# windows +ELSE(UNIX) + SET(PROJECT_LIBS + # add any extra windows libs here + lua + dfhack-tinythread + ${PROJECT_LIBS} + $(NOINHERIT) + ) +ENDIF(UNIX) +# this makes sure all the stuff is put in proper places and linked to dfhack +DFHACK_PLUGIN(rendermax ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) +install(FILES rendermax.lua + DESTINATION ${DFHACK_DATA_DESTINATION}/raw) diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp new file mode 100644 index 000000000..395e12c14 --- /dev/null +++ b/plugins/rendermax/renderer_light.cpp @@ -0,0 +1,1500 @@ +#include "renderer_light.hpp" + +#include +#include +#include + +#include "tinythread.h" + +#include "LuaTools.h" + +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "modules/Maps.h" + +#include "modules/Units.h" + +#include "df/graphic.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_dungeonmodest.h" +#include "df/flow_info.h" +#include "df/world.h" +#include "df/building.h" +#include "df/building_doorst.h" +#include "df/building_floodgatest.h" +#include "df/plant.h" +#include "df/plant_raw.h" +#include "df/item.h" +#include "df/items_other_id.h" +#include "df/unit.h" + +#include + +using df::global::gps; +using namespace DFHack; +using df::coord2d; +using namespace tthread; + +const float RootTwo = 1.4142135623730950488016887242097f; + + +bool isInRect(const coord2d& pos,const rect2d& rect) +{ + if(pos.x>=rect.first.x && pos.y>=rect.first.y && pos.x= 0) + this->radius = radius; + else + { + float levelDim = 0.2f;//TODO this is not correct if you change config + float totalPower = power.r; + if(totalPower < power.g)totalPower = power.g; + if(totalPower < power.b)totalPower = power.b; + if(totalPower > 0 && levelDim > 0) + this->radius = (int)((log(levelDim/totalPower)/log(0.85f))) + 1; + else + this->radius = 0; + } +} + +rect2d getMapViewport() +{ + const int AREA_MAP_WIDTH = 23; + const int MENU_WIDTH = 30; + if(!gps || !df::viewscreen_dwarfmodest::_identity.is_instance(DFHack::Gui::getCurViewscreen())) + { + if(gps && df::viewscreen_dungeonmodest::_identity.is_instance(DFHack::Gui::getCurViewscreen())) + { + return mkrect_wh(0,0,gps->dimx,gps->dimy); + } + else + return mkrect_wh(0,0,0,0); + + } + int w=gps->dimx; + int h=gps->dimy; + int view_height=h-2; + int area_x2 = w-AREA_MAP_WIDTH-2; + int menu_x2=w-MENU_WIDTH-2; + int menu_x1=area_x2-MENU_WIDTH-1; + int view_rb=w-1; + + int area_pos=*df::global::ui_area_map_width; + int menu_pos=*df::global::ui_menu_width; + if(area_pos<3) + { + view_rb=area_x2; + } + if (menu_posmain.mode!=0) + { + if (menu_pos >= area_pos) + menu_pos = area_pos-1; + int menu_x = menu_x2; + if(menu_pos < 2) menu_x = menu_x1; + view_rb = menu_x; + } + return mkrect_wh(1,1,view_rb,view_height+1); +} +lightingEngineViewscreen::lightingEngineViewscreen(renderer_light* target):lightingEngine(target),doDebug(false),threading(this) +{ + reinit(); + defaultSettings(); + int numTreads=tthread::thread::hardware_concurrency(); + if(numTreads==0)numTreads=1; + threading.start(numTreads); +} + +void lightingEngineViewscreen::reinit() +{ + if(!gps) + return; + w=gps->dimx; + h=gps->dimy; + size_t size=w*h; + lightMap.resize(size,rgbf(1,1,1)); + ocupancy.resize(size); + lights.resize(size); +} + +void plotCircle(int xm, int ym, int r,const std::function& setPixel) +{ + int x = -r, y = 0, err = 2-2*r; /* II. Quadrant */ + do { + setPixel(xm-x, ym+y); /* I. Quadrant */ + setPixel(xm-y, ym-x); /* II. Quadrant */ + setPixel(xm+x, ym-y); /* III. Quadrant */ + setPixel(xm+y, ym+x); /* IV. Quadrant */ + r = err; + if (r <= y) err += ++y*2+1; /* e_xy+e_y < 0 */ + if (r > x || err > y) err += ++x*2+1; /* e_xy+e_x > 0 or no 2nd y-step */ + } while (x < 0); +} +void plotSquare(int xm, int ym, int r,const std::function& setPixel) +{ + for(int x = 0; x <= r; x++) + { + setPixel(xm+r, ym+x); /* I.1 Quadrant */ + setPixel(xm+x, ym+r); /* I.2 Quadrant */ + setPixel(xm+r, ym-x); /* II.1 Quadrant */ + setPixel(xm+x, ym-r); /* II.2 Quadrant */ + setPixel(xm-r, ym-x); /* III.1 Quadrant */ + setPixel(xm-x, ym-r); /* III.2 Quadrant */ + setPixel(xm-r, ym+x); /* IV.1 Quadrant */ + setPixel(xm-x, ym+r); /* IV.2 Quadrant */ + } +} +void plotLine(int x0, int y0, int x1, int y1,rgbf power,const std::function& setPixel) +{ + int dx = abs(x1-x0), sx = x0= dy) { err += dy; x0 += sx; rdx=sx;} /* e_xy+e_x > 0 */ + if (e2 <= dx) { err += dx; y0 += sy; rdy=sy;} /* e_xy+e_y < 0 */ + } + return ; +} +void plotLineDiffuse(int x0, int y0, int x1, int y1,rgbf power,int num_diffuse,const std::function& setPixel,bool skip_hack=false) +{ + + int dx = abs(x1-x0), sx = x0= dy) { err += dy; x0 += sx; rdx=sx;} /* e_xy+e_x > 0 */ + if (e2 <= dx) { err += dx; y0 += sy; rdy=sy;} /* e_xy+e_y < 0 */ + + if(num_diffuse>0 && dsq/4<(x1-x0)*(x1-x0)+(y1-y0)*(y1-y0))//reached center? + { + const float betta=0.25; + int nx=y1-y0; //right angle + int ny=x1-x0; + if((nx*nx+ny*ny)*betta*betta>2) + { + plotLineDiffuse(x0,y0,x0+nx*betta,y0+ny*betta,power,num_diffuse-1,setPixel,true); + plotLineDiffuse(x0,y0,x0-nx*betta,y0-ny*betta,power,num_diffuse-1,setPixel,true); + } + } + } + return ; +} +void plotLineAA(int x0, int y0, int x1, int y1,rgbf power,const std::function& setPixelAA) +{ + int dx = abs(x1-x0), sx = x0= -dx) { /* x step */ + if (x0 == x1) break; + + if (e2+dy < ed) + { + str=1-(e2+dy)/(float)ed; + sumPower+=setPixelAA(power*str,lrdx,lrdy,x0,y0+sy); + strsum+=str; + } + err -= dy; x0 += sx; rdx=sx; + } + if (2*e2 <= dy) { /* y step */ + if (y0 == y1) break; + + if (dx-e2 < ed) + { + str=1-(dx-e2)/(float)ed; + sumPower+=setPixelAA(power*str,lrdx,lrdy,x2+sx,y0); + strsum+=str; + } + err += dx; y0 += sy; rdy=sy; + } + if(strsum<0.001f) + return; + sumPower=sumPower/strsum; + if(sumPower.dot(sumPower)<0.00001f) + return; + power=sumPower; + } +} +rgbf blendMax(const rgbf& a,const rgbf& b) +{ + return rgbf(std::max(a.r,b.r),std::max(a.g,b.g),std::max(a.b,b.b)); +} +rgbf blend(const rgbf& a,const rgbf& b) +{ + return blendMax(a,b); +} +void lightingEngineViewscreen::clear() +{ + lightMap.assign(lightMap.size(),rgbf(1,1,1)); + tthread::lock_guard guard(myRenderer->dataMutex); + if(lightMap.size()==myRenderer->lightGrid.size()) + { + std::swap(myRenderer->lightGrid,lightMap); + myRenderer->invalidate(); + } +} +void lightingEngineViewscreen::calculate() +{ + if(lightMap.size()!=myRenderer->lightGrid.size()) + { + reinit(); + myRenderer->invalidate();//needs a lock? + } + rect2d vp=getMapViewport(); + const rgbf dim(levelDim,levelDim,levelDim); + lightMap.assign(lightMap.size(),rgbf(1,1,1)); + lights.assign(lights.size(),lightSource()); + for(int i=vp.first.x;i guard(myRenderer->dataMutex); + if(lightMap.size()!=myRenderer->lightGrid.size()) + { + reinit(); + myRenderer->invalidate(); + return; + } + + bool isAdventure=(*df::global::gametype==df::game_type::ADVENTURE_ARENA)|| + (*df::global::gametype==df::game_type::ADVENTURE_MAIN); + if(isAdventure) + { + fixAdvMode(adv_mode); + } + + if(doDebug) + std::swap(ocupancy,myRenderer->lightGrid); + else + std::swap(lightMap,myRenderer->lightGrid); + rect2d vp=getMapViewport(); + + myRenderer->invalidateRect(vp.first.x,vp.first.y,vp.second.x-vp.first.x,vp.second.y-vp.first.y); +} +void lightingEngineViewscreen::preRender() +{ + +} +void lightingEngineViewscreen::fixAdvMode(int mode) +{ + + MapExtras::MapCache mc; + const rgbf dim(levelDim,levelDim,levelDim); + rect2d vp=getMapViewport(); + int window_x=*df::global::window_x; + int window_y=*df::global::window_y; + int window_z=*df::global::window_z; + coord2d vpSize=rect_size(vp); + //mode 0-> make dark non-visible parts + if(mode==0) + { + for(int x=vp.first.x;x make everything visible, let the lighting hide stuff + else if(mode==1) + { + for(int x=vp.first.x;x0; + lights[tileId].combine(light); + if(light.flicker) + lights[tileId].flicker=true; + return wasLight; +} +void lightingEngineViewscreen::addOclusion(int tileId,const rgbf& c,float thickness) +{ + if(thickness > 0.999 && thickness < 1.001) + ocupancy[tileId]*=c; + else + ocupancy[tileId]*=(c.pow(thickness)); +} +rgbf getStandartColor(int colorId) +{ + return rgbf(df::global::enabler->ccolor[colorId][0]/255.0f, + df::global::enabler->ccolor[colorId][1]/255.0f, + df::global::enabler->ccolor[colorId][2]/255.0f); +} +int getPlantNumber(const std::string& id) +{ + std::vector& vec=df::plant_raw::get_vector(); + for(int i=0;iid==id) + return i; + } + return -1; +} +void addPlant(const std::string& id,std::map& map,const lightSource& v) +{ + int nId=getPlantNumber(id); + if(nId>0) + { + map[nId]=v; + } +} +matLightDef* lightingEngineViewscreen::getMaterialDef( int matType,int matIndex ) +{ + auto it=matDefs.find(std::make_pair(matType,matIndex)); + if(it!=matDefs.end()) + return &it->second; + else + return NULL; +} +buildingLightDef* lightingEngineViewscreen::getBuildingDef( df::building* bld ) +{ + auto it=buildingDefs.find(std::make_tuple((int)bld->getType(),(int)bld->getSubtype(),(int)bld->getCustomType())); + if(it!=buildingDefs.end()) + return &it->second; + else + return NULL; +} +creatureLightDef* lightingEngineViewscreen::getCreatureDef(df::unit* u) +{ + auto it=creatureDefs.find(std::make_pair(int(u->race),int(u->caste))); + if(it!=creatureDefs.end()) + return &it->second; + else + { + auto it2=creatureDefs.find(std::make_pair(int(u->race),int(-1))); + if(it2!=creatureDefs.end()) + return &it2->second; + else + return NULL; + } +} +itemLightDef* lightingEngineViewscreen::getItemDef(df::item* it) +{ + auto iter=itemDefs.find(std::make_pair(int(it->getType()),int(it->getSubtype()))); + if(iter!=itemDefs.end()) + return &iter->second; + else + { + auto iter2=itemDefs.find(std::make_pair(int(it->getType()),int(-1))); + if(iter2!=itemDefs.end()) + return &iter2->second; + else + return NULL; + } +} +void lightingEngineViewscreen::applyMaterial(int tileId,const matLightDef& mat,float size, float thickness) +{ + if(mat.isTransparent) + { + addOclusion(tileId,mat.transparency,thickness); + } + else + ocupancy[tileId]=rgbf(0,0,0); + if(mat.isEmiting) + addLight(tileId,mat.makeSource(size)); +} +bool lightingEngineViewscreen::applyMaterial(int tileId,int matType,int matIndex,float size,float thickness,const matLightDef* def) +{ + matLightDef* m=getMaterialDef(matType,matIndex); + if(m) + { + applyMaterial(tileId,*m,size,thickness); + return true; + } + else if(def) + { + applyMaterial(tileId,*def,size,thickness); + } + return false; +} +rgbf lightingEngineViewscreen::propogateSun(MapExtras::Block* b, int x,int y,const rgbf& in,bool lastLevel) +{ + //TODO unify under addLight/addOclusion + const rgbf matStairCase(0.9f,0.9f,0.9f); + rgbf ret=in; + coord2d innerCoord(x,y); + df::tiletype type = b->staticTiletypeAt(innerCoord); + df::tile_designation d = b->DesignationAt(innerCoord); + //df::tile_occupancy o = b->OccupancyAt(innerCoord); + df::tiletype_shape shape = ENUM_ATTR(tiletype,shape,type); + df::tiletype_shape_basic basic_shape = ENUM_ATTR(tiletype_shape, basic_shape, shape); + DFHack::t_matpair mat=b->staticMaterialAt(innerCoord); + df::tiletype_material tileMat= ENUM_ATTR(tiletype,material,type); + + matLightDef* lightDef; + if(tileMat==df::tiletype_material::FROZEN_LIQUID) + { + df::tiletype typeIce = b->tiletypeAt(innerCoord); + df::tiletype_shape shapeIce = ENUM_ATTR(tiletype,shape,typeIce); + df::tiletype_shape_basic basicShapeIce = ENUM_ATTR(tiletype_shape,basic_shape,shapeIce); + if(basicShapeIce==df::tiletype_shape_basic::Wall) + ret*=matIce.transparency; + else if(basicShapeIce==df::tiletype_shape_basic::Floor || basicShapeIce==df::tiletype_shape_basic::Ramp || shapeIce==df::tiletype_shape::STAIR_UP) + if(!lastLevel) + ret*=matIce.transparency.pow(1.0f/7.0f); + } + + lightDef=getMaterialDef(mat.mat_type,mat.mat_index); + + if(!lightDef || !lightDef->isTransparent) + lightDef=&matWall; + if(basic_shape==df::tiletype_shape_basic::Wall) + { + ret*=lightDef->transparency; + } + else if(basic_shape==df::tiletype_shape_basic::Floor || basic_shape==df::tiletype_shape_basic::Ramp || shape==df::tiletype_shape::STAIR_UP) + { + + if(!lastLevel) + ret*=lightDef->transparency.pow(1.0f/7.0f); + } + else if(shape==df::tiletype_shape::STAIR_DOWN || shape==df::tiletype_shape::STAIR_UPDOWN) + { + ret*=matStairCase; + } + if(d.bits.liquid_type == df::enums::tile_liquid::Water && d.bits.flow_size > 0) + { + ret *=matWater.transparency.pow((float)d.bits.flow_size/7.0f); + } + else if(d.bits.liquid_type == df::enums::tile_liquid::Magma && d.bits.flow_size > 0) + { + ret *=matLava.transparency.pow((float)d.bits.flow_size/7.0f); + } + return ret; +} +coord2d lightingEngineViewscreen::worldToViewportCoord(const coord2d& in,const rect2d& r,const coord2d& window2d) +{ + return in-window2d+r.first; +} + +static size_t max_list_size = 100000; // Avoid iterating over huge lists +void lightingEngineViewscreen::doSun(const lightSource& sky,MapExtras::MapCache& map) +{ + //TODO fix this mess + int window_x=*df::global::window_x; + int window_y=*df::global::window_y; + coord2d window2d(window_x,window_y); + int window_z=*df::global::window_z; + rect2d vp=getMapViewport(); + coord2d vpSize=rect_size(vp); + rect2d blockVp; + blockVp.first=window2d/16; + blockVp.second=(window2d+vpSize)/16; + blockVp.second.x=std::min(blockVp.second.x,(int16_t)df::global::world->map.x_count_block); + blockVp.second.y=std::min(blockVp.second.y,(int16_t)df::global::world->map.y_count_block); + //endof mess + for(int blockX=blockVp.first.x;blockX<=blockVp.second.x;blockX++) + for(int blockY=blockVp.first.y;blockY<=blockVp.second.y;blockY++) + { + rgbf cellArray[16][16]; + for(int block_x = 0; block_x < 16; block_x++) + for(int block_y = 0; block_y < 16; block_y++) + cellArray[block_x][block_y] = sky.power; + + int emptyCell=0; + for(int z=window_z;z< df::global::world->map.z_count && emptyCell<256;z++) + { + MapExtras::Block* b=map.BlockAt(DFCoord(blockX,blockY,z)); + if(!b) + continue; + emptyCell=0; + for(int block_x = 0; block_x < 16; block_x++) + for(int block_y = 0; block_y < 16; block_y++) + { + rgbf& curCell=cellArray[block_x][block_y]; + curCell=propogateSun(b,block_x,block_y,curCell,z==window_z); + if(curCell.dot(curCell)<0.003f) + emptyCell++; + } + } + if(emptyCell==256) + continue; + for(int block_x = 0; block_x < 16; block_x++) + for(int block_y = 0; block_y < 16; block_y++) + { + rgbf& curCell=cellArray[block_x][block_y]; + df::coord2d pos; + pos.x = blockX*16+block_x; + pos.y = blockY*16+block_y; + pos=worldToViewportCoord(pos,vp,window2d); + if(isInRect(pos,vp) && curCell.dot(curCell)>0.003f) + { + lightSource sun=lightSource(curCell,15); + addLight(getIndex(pos.x,pos.y),sun); + } + } + } +} +rgbf lightingEngineViewscreen::getSkyColor(float v) +{ + if(dayColors.size()<2) + { + v=abs(fmod(v+0.5,1)-0.5)*2; + return rgbf(v,v,v); + } + else + { + float pos=v*(dayColors.size()-1); + int pre=floor(pos); + pos-=pre; + if(pre==dayColors.size()-1) + return dayColors[pre]; + return dayColors[pre]*(1-pos)+dayColors[pre+1]*pos; + } +} +void lightingEngineViewscreen::doOcupancyAndLights() +{ + float daycol; + if(dayHour<0) + { + int length=1200/daySpeed; + daycol= (*df::global::cur_year_tick % length)/ (float)length; + } + else + daycol= fmod(dayHour,24.0f)/24.0f; //1->12h 0->24h + + rgbf sky_col=getSkyColor(daycol); + lightSource sky(sky_col, -1);//auto calculate best size + + MapExtras::MapCache cache; + doSun(sky,cache); + + int window_x=*df::global::window_x; + int window_y=*df::global::window_y; + coord2d window2d(window_x,window_y); + int window_z=*df::global::window_z; + rect2d vp=getMapViewport(); + coord2d vpSize=rect_size(vp); + rect2d blockVp; + blockVp.first=coord2d(window_x,window_y)/16; + blockVp.second=(window2d+vpSize)/16; + blockVp.second.x=std::min(blockVp.second.x,(int16_t)df::global::world->map.x_count_block); + blockVp.second.y=std::min(blockVp.second.y,(int16_t)df::global::world->map.y_count_block); + + for(int blockX=blockVp.first.x;blockX<=blockVp.second.x;blockX++) + for(int blockY=blockVp.first.y;blockY<=blockVp.second.y;blockY++) + { + MapExtras::Block* b=cache.BlockAt(DFCoord(blockX,blockY,window_z)); + MapExtras::Block* bDown=cache.BlockAt(DFCoord(blockX,blockY,window_z-1)); + if(!b) + continue; //empty blocks fixed by sun propagation + + for(int block_x = 0; block_x < 16; block_x++) + for(int block_y = 0; block_y < 16; block_y++) + { + df::coord2d pos; + pos.x = blockX*16+block_x; + pos.y = blockY*16+block_y; + df::coord2d gpos=pos; + pos=worldToViewportCoord(pos,vp,window2d); + if(!isInRect(pos,vp)) + continue; + int tile=getIndex(pos.x,pos.y); + rgbf& curCell=ocupancy[tile]; + curCell=matAmbience.transparency; + + + df::tiletype type = b->tiletypeAt(gpos); + df::tile_designation d = b->DesignationAt(gpos); + if(d.bits.hidden ) + { + curCell=rgbf(0,0,0); + continue; // do not process hidden stuff, TODO other hidden stuff + } + //df::tile_occupancy o = b->OccupancyAt(gpos); + df::tiletype_shape shape = ENUM_ATTR(tiletype,shape,type); + df::tiletype_shape_basic basic_shape = ENUM_ATTR(tiletype_shape, basic_shape, shape); + df::tiletype_material tileMat= ENUM_ATTR(tiletype,material,type); + + DFHack::t_matpair mat=b->staticMaterialAt(gpos); + + matLightDef* lightDef=getMaterialDef(mat.mat_type,mat.mat_index); + if(!lightDef || !lightDef->isTransparent) + lightDef=&matWall; + if(shape==df::tiletype_shape::BROOK_BED ) + { + curCell=rgbf(0,0,0); + } + else if(shape==df::tiletype_shape::WALL) + { + if(tileMat==df::tiletype_material::FROZEN_LIQUID) + applyMaterial(tile,matIce); + else + applyMaterial(tile,*lightDef); + } + else if(!d.bits.liquid_type && d.bits.flow_size>0 ) + { + applyMaterial(tile,matWater, (float)d.bits.flow_size/7.0f, (float)d.bits.flow_size/7.0f); + } + if(d.bits.liquid_type && d.bits.flow_size>0) + { + applyMaterial(tile,matLava,(float)d.bits.flow_size/7.0f,(float)d.bits.flow_size/7.0f); + } + else if(shape==df::tiletype_shape::EMPTY || shape==df::tiletype_shape::RAMP_TOP + || shape==df::tiletype_shape::STAIR_DOWN || shape==df::tiletype_shape::STAIR_UPDOWN) + { + if(bDown) + { + df::tile_designation d2=bDown->DesignationAt(gpos); + if(d2.bits.liquid_type && d2.bits.flow_size>0) + { + applyMaterial(tile,matLava); + } + } + } + + + } + + df::map_block* block=b->getRaw(); + if(!block) + continue; + //flows + for(int i=0;iflows.size();i++) + { + df::flow_info* f=block->flows[i]; + if(f && f->density>0 && f->type==df::flow_type::Dragonfire || f->type==df::flow_type::Fire) + { + df::coord2d pos=f->pos; + pos=worldToViewportCoord(pos,vp,window2d); + int tile=getIndex(pos.x,pos.y); + if(isInRect(pos,vp)) + { + rgbf fireColor; + if(f->density>60) + { + fireColor=rgbf(0.98f,0.91f,0.30f); + } + else if(f->density>30) + { + fireColor=rgbf(0.93f,0.16f,0.16f); + } + else + { + fireColor=rgbf(0.64f,0.0f,0.0f); + } + lightSource fire(fireColor,f->density/5); + addLight(tile,fire); + } + } + } + + //plants + for(int i=0;iplants.size();i++) + { + df::plant* cPlant=block->plants[i]; + if (cPlant->grow_counter <180000) //todo maybe smaller light/oclusion? + continue; + df::coord2d pos=cPlant->pos; + pos=worldToViewportCoord(pos,vp,window2d); + int tile=getIndex(pos.x,pos.y); + if(isInRect(pos,vp)) + { + applyMaterial(tile,419,cPlant->material); + } + } + //blood and other goo + for(int i=0;iblock_events.size();i++) + { + df::block_square_event* ev=block->block_events[i]; + df::block_square_event_type ev_type=ev->getType(); + if(ev_type==df::block_square_event_type::material_spatter) + { + df::block_square_event_material_spatterst* spatter=static_cast(ev); + matLightDef* m=getMaterialDef(spatter->mat_type,spatter->mat_index); + if(!m) + continue; + if(!m->isEmiting) + continue; + for(int x=0;x<16;x++) + for(int y=0;y<16;y++) + { + df::coord2d pos; + pos.x = blockX*16+x; + pos.y = blockY*16+y; + int16_t amount=spatter->amount[x][y]; + if(amount<=0) + continue; + pos=worldToViewportCoord(pos,vp,window2d); + if(isInRect(pos,vp)) + { + addLight(getIndex(pos.x,pos.y),m->makeSource((float)amount/100)); + } + } + } + } + } + if(df::global::cursor->x>-30000) + { + int wx=df::global::cursor->x-window_x+vp.first.x; + int wy=df::global::cursor->y-window_y+vp.first.y; + int tile=getIndex(wx,wy); + applyMaterial(tile,matCursor); + } + //citizen only emit light, if defined + //or other creatures + if(matCitizen.isEmiting || creatureDefs.size()>0) + for (int i=0;iunits.active.size();++i) + { + df::unit *u = df::global::world->units.active[i]; + coord2d pos=worldToViewportCoord(coord2d(u->pos.x,u->pos.y),vp,window2d); + if(u->pos.z==window_z && isInRect(pos,vp)) + { + if (DFHack::Units::isCitizen(u) && !u->counters.unconscious) + addLight(getIndex(pos.x,pos.y),matCitizen.makeSource()); + creatureLightDef *def=getCreatureDef(u); + if(def && !u->flags1.bits.dead) + { + addLight(getIndex(pos.x,pos.y),def->light.makeSource()); + } + } + } + //items + if(itemDefs.size()>0) + { + std::vector& vec=df::global::world->items.other[items_other_id::IN_PLAY]; + for(size_t i=0;iequiped || mat->haul ||mat->inBuilding ||mat->inContainer) && curItem->flags.bits.in_inventory)|| //TODO split this up + (mat->onGround && curItem->flags.bits.on_ground) ) + { + if(mat->light.isEmiting) + addLight(getIndex(pos.x,pos.y),mat->light.makeSource()); + if(!mat->light.isTransparent) + addOclusion(getIndex(pos.x,pos.y),mat->light.transparency,1); + } + } + } + } + + //buildings + for(size_t i = 0; i < df::global::world->buildings.all.size(); i++) + { + df::building *bld = df::global::world->buildings.all[i]; + + if(window_z!=bld->z) + continue; + if(bld->getBuildStage()getMaxBuildStage()) //only work if fully built + continue; + + df::coord2d p1(bld->x1,bld->y1); + df::coord2d p2(bld->x2,bld->y2); + p1=worldToViewportCoord(p1,vp,window2d); + p2=worldToViewportCoord(p2,vp,window2d); + if(isInRect(p1,vp)||isInRect(p2,vp)) + { + + int tile; + if(isInRect(p1,vp)) + tile=getIndex(p1.x,p1.y); //TODO multitile buildings. How they would work? + else + tile=getIndex(p2.x,p2.y); + df::building_type type = bld->getType(); + buildingLightDef* def=getBuildingDef(bld); + if(!def) + continue; + if(type==df::enums::building_type::Door) + { + df::building_doorst* door=static_cast(bld); + if(!door->door_flags.bits.closed) + continue; + } + else if(type==df::enums::building_type::Floodgate) + { + df::building_floodgatest* gate=static_cast(bld); + if(!gate->gate_flags.bits.closed) + continue; + } + + + if(def->useMaterial) + { + matLightDef* mat=getMaterialDef(bld->mat_type,bld->mat_index); + if(!mat)mat=&matWall; + if(!def->poweredOnly || !bld->isUnpowered()) //not powered. Add occlusion only. + { + if(def->light.isEmiting) + { + addLight(tile,def->light.makeSource(def->size)); + } + else if(mat->isEmiting) + { + addLight(tile,mat->makeSource(def->size)); + } + } + if(def->light.isTransparent) + { + addOclusion(tile,def->light.transparency,def->size); + } + else + { + addOclusion(tile,mat->transparency,def->size); + } + } + else + { + if(!def->poweredOnly || !bld->isUnpowered())//not powered. Add occlusion only. + addOclusion(tile,def->light.transparency,def->size); + else + applyMaterial(tile,def->light,def->size,def->thickness); + } + } + } + +} +rgbf lua_parseLightCell(lua_State* L) +{ + rgbf ret; + + lua_pushnumber(L,1); + lua_gettable(L,-2); + ret.r=lua_tonumber(L,-1); + lua_pop(L,1); + + lua_pushnumber(L,2); + lua_gettable(L,-2); + ret.g=lua_tonumber(L,-1); + lua_pop(L,1); + + lua_pushnumber(L,3); + lua_gettable(L,-2); + ret.b=lua_tonumber(L,-1); + lua_pop(L,1); + + //Lua::GetOutput(L)->print("got cell(%f,%f,%f)\n",ret.r,ret.g,ret.b); + return ret; +} +#define GETLUAFLAG(field,name) lua_getfield(L,-1,"flags");\ + if(lua_isnil(L,-1)){field=false;}\ + else{lua_getfield(L,-1,#name);field=lua_isnil(L,-1);lua_pop(L,1);}\ + lua_pop(L,1) + +#define GETLUANUMBER(field,name) lua_getfield(L,-1,#name);\ + if(!lua_isnil(L,-1) && lua_isnumber(L,-1))field=lua_tonumber(L,-1);\ + lua_pop(L,1) +matLightDef lua_parseMatDef(lua_State* L) +{ + + matLightDef ret; + lua_getfield(L,-1,"tr"); + if(ret.isTransparent=!lua_isnil(L,-1)) + { + ret.transparency=lua_parseLightCell(L); + } + lua_pop(L,1); + + lua_getfield(L,-1,"em"); + if(ret.isEmiting=!lua_isnil(L,-1)) + { + ret.emitColor=lua_parseLightCell(L); + lua_pop(L,1); + lua_getfield(L,-1,"rad"); + if(lua_isnil(L,-1)) + { + lua_pop(L,1); + luaL_error(L,"Material has emittance but no radius"); + } + else + ret.radius=lua_tonumber(L,-1); + lua_pop(L,1); + } + else + lua_pop(L,1); + GETLUAFLAG(ret.flicker,"flicker"); + return ret; +} +int lightingEngineViewscreen::parseMaterials(lua_State* L) +{ + auto engine= (lightingEngineViewscreen*)lua_touserdata(L, 1); + engine->matDefs.clear(); + //color_ostream* os=Lua::GetOutput(L); + Lua::StackUnwinder unwinder(L); + lua_getfield(L,2,"materials"); + if(!lua_istable(L,-1)) + { + luaL_error(L,"Materials table not found."); + return 0; + } + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + int type=lua_tonumber(L,-2); + //os->print("Processing type:%d\n",type); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + int index=lua_tonumber(L,-2); + //os->print("\tProcessing index:%d\n",index); + engine->matDefs[std::make_pair(type,index)]=lua_parseMatDef(L); + + lua_pop(L, 1); + } + lua_pop(L, 1); + } + return 0; +} +#define LOAD_SPECIAL(lua_name,class_name) \ + lua_getfield(L,-1,#lua_name);\ + if(!lua_isnil(L,-1))engine->class_name=lua_parseMatDef(L);\ + lua_pop(L,1) +int lightingEngineViewscreen::parseSpecial(lua_State* L) +{ + auto engine= (lightingEngineViewscreen*)lua_touserdata(L, 1); + Lua::StackUnwinder unwinder(L); + lua_getfield(L,2,"special"); + if(!lua_istable(L,-1)) + { + luaL_error(L,"Special table not found."); + return 0; + } + LOAD_SPECIAL(LAVA,matLava); + LOAD_SPECIAL(WATER,matWater); + LOAD_SPECIAL(FROZEN_LIQUID,matIce); + LOAD_SPECIAL(AMBIENT,matAmbience); + LOAD_SPECIAL(CURSOR,matCursor); + LOAD_SPECIAL(CITIZEN,matCitizen); + GETLUANUMBER(engine->levelDim,levelDim); + GETLUANUMBER(engine->dayHour,dayHour); + GETLUANUMBER(engine->daySpeed,daySpeed); + GETLUANUMBER(engine->num_diffuse,diffusionCount); + GETLUANUMBER(engine->adv_mode,advMode); + lua_getfield(L,-1,"dayColors"); + if(lua_istable(L,-1)) + { + engine->dayColors.clear(); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + engine->dayColors.push_back(lua_parseLightCell(L)); + lua_pop(L,1); + } + lua_pop(L,1); + } + return 0; +} +#undef LOAD_SPECIAL +int lightingEngineViewscreen::parseItems(lua_State* L) +{ + auto engine= (lightingEngineViewscreen*)lua_touserdata(L, 1); + engine->itemDefs.clear(); + Lua::StackUnwinder unwinder(L); + lua_getfield(L,2,"items"); + if(!lua_istable(L,-1)) + { + luaL_error(L,"Items table not found."); + return 0; + } + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if(!lua_istable(L,-1)) + luaL_error(L,"Broken item definitions."); + lua_getfield(L,-1,"type"); + int type=lua_tonumber(L,-1); + lua_pop(L,1); + lua_getfield(L,-1,"subtype"); + int subtype=luaL_optinteger(L,-1,-1); + lua_pop(L,1); + itemLightDef item; + lua_getfield(L,-1,"light"); + item.light=lua_parseMatDef(L); + GETLUAFLAG(item.haul,"hauling"); + GETLUAFLAG(item.equiped,"equiped"); + GETLUAFLAG(item.inBuilding,"inBuilding"); + GETLUAFLAG(item.inContainer,"contained"); + GETLUAFLAG(item.onGround,"onGround"); + GETLUAFLAG(item.useMaterial,"useMaterial"); + engine->itemDefs[std::make_pair(type,subtype)]=item; + lua_pop(L,2); + } + lua_pop(L,1); + return 0; +} +int lightingEngineViewscreen::parseCreatures(lua_State* L) +{ + auto engine= (lightingEngineViewscreen*)lua_touserdata(L, 1); + engine->creatureDefs.clear(); + Lua::StackUnwinder unwinder(L); + lua_getfield(L,2,"creatures"); + if(!lua_istable(L,-1)) + { + luaL_error(L,"Creatures table not found."); + return 0; + } + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if(!lua_istable(L,-1)) + luaL_error(L,"Broken creature definitions."); + lua_getfield(L,-1,"race"); + int race=lua_tonumber(L,-1); + lua_pop(L,1); + lua_getfield(L,-1,"caste"); + int caste=lua_tonumber(L,-1); + lua_pop(L,1); + creatureLightDef cr; + lua_getfield(L,-1,"light"); + cr.light=lua_parseMatDef(L); + engine->creatureDefs[std::make_pair(race,caste)]=cr; + lua_pop(L,2); + } + lua_pop(L,1); + return 0; +} +int lightingEngineViewscreen::parseBuildings(lua_State* L) +{ + auto engine= (lightingEngineViewscreen*)lua_touserdata(L, 1); + engine->buildingDefs.clear(); + Lua::StackUnwinder unwinder(L); + lua_getfield(L,2,"buildings"); + if(!lua_istable(L,-1)) + { + luaL_error(L,"Buildings table not found."); + return 0; + } + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + int type=lua_tonumber(L,-2); + if(!lua_istable(L,-1)) + { + luaL_error(L,"Broken building definitions."); + } + //os->print("Processing type:%d\n",type); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + int subtype=lua_tonumber(L,-2); + //os->print("\tProcessing subtype:%d\n",index); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + int custom=lua_tonumber(L,-2); + //os->print("\tProcessing custom:%d\n",index); + buildingLightDef current; + current.light=lua_parseMatDef(L); + engine->buildingDefs[std::make_tuple(type,subtype,custom)]=current; + GETLUAFLAG(current.poweredOnly,"poweredOnly"); + GETLUAFLAG(current.useMaterial,"useMaterial"); + + lua_getfield(L,-1,"size"); + current.size=luaL_optnumber(L,-1,1); + lua_pop(L,1); + + lua_getfield(L,-1,"thickness"); + current.thickness=luaL_optnumber(L,-1,1); + lua_pop(L,1); + + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + lua_pop(L, 1); + } + lua_pop(L,1); + return 0; +} +void lightingEngineViewscreen::defaultSettings() +{ + matAmbience=matLightDef(rgbf(0.85f,0.85f,0.85f)); + matLava=matLightDef(rgbf(0.8f,0.2f,0.2f),rgbf(0.8f,0.2f,0.2f),5); + matWater=matLightDef(rgbf(0.6f,0.6f,0.8f)); + matIce=matLightDef(rgbf(0.7f,0.7f,0.9f)); + matCursor=matLightDef(rgbf(0.96f,0.84f,0.03f),11); + matCursor.flicker=true; + matWall=matLightDef(rgbf(0,0,0)); + matCitizen=matLightDef(rgbf(0.8f,0.8f,0.9f),6); + levelDim=0.2f; + dayHour=-1; + daySpeed=1; + adv_mode=0; + num_diffuse=0; + dayColors.push_back(rgbf(0,0,0)); + dayColors.push_back(rgbf(1,1,1)); + dayColors.push_back(rgbf(0,0,0)); +} +void lightingEngineViewscreen::loadSettings() +{ + std::string rawFolder; + if(df::global::world->cur_savegame.save_dir!="") + { + rawFolder= "data/save/" + (df::global::world->cur_savegame.save_dir) + "/raw/"; + } + else + { + rawFolder= "raw/"; + } + const std::string settingsfile=rawFolder+"rendermax.lua"; + + CoreSuspender lock; + color_ostream_proxy out(Core::getInstance().getConsole()); + + lua_State* s=DFHack::Lua::Core::State; + lua_newtable(s); + int env=lua_gettop(s); + try{ + int ret=luaL_loadfile(s,settingsfile.c_str()); + if(ret==LUA_ERRFILE) + { + out.printerr("File not found:%s\n",settingsfile.c_str()); + lua_pop(s,1); + } + else if(ret==LUA_ERRSYNTAX) + { + out.printerr("Syntax error:\n\t%s\n",lua_tostring(s,-1)); + } + else + { + lua_pushvalue(s,env); + + if(Lua::SafeCall(out,s,1,0)) + { + lua_pushcfunction(s, parseMaterials); + lua_pushlightuserdata(s, this); + lua_pushvalue(s,env); + Lua::SafeCall(out,s,2,0); + out.print("%d materials loaded\n",matDefs.size()); + + lua_pushcfunction(s, parseSpecial); + lua_pushlightuserdata(s, this); + lua_pushvalue(s,env); + Lua::SafeCall(out,s,2,0); + out.print("%d day light colors loaded\n",dayColors.size()); + + lua_pushcfunction(s, parseBuildings); + lua_pushlightuserdata(s, this); + lua_pushvalue(s,env); + Lua::SafeCall(out,s,2,0); + out.print("%d buildings loaded\n",buildingDefs.size()); + + lua_pushcfunction(s, parseCreatures); + lua_pushlightuserdata(s, this); + lua_pushvalue(s,env); + Lua::SafeCall(out,s,2,0); + out.print("%d creatures loaded\n",creatureDefs.size()); + + lua_pushcfunction(s, parseItems); + lua_pushlightuserdata(s, this); + lua_pushvalue(s,env); + Lua::SafeCall(out,s,2,0); + out.print("%d items loaded\n",itemDefs.size()); + } + + } + } + catch(std::exception& e) + { + out.printerr("%s",e.what()); + } + lua_pop(s,1); +} +#undef GETLUAFLAG +#undef GETLUANUMBER +/* + * Threading stuff + */ +lightThread::lightThread( lightThreadDispatch& dispatch ):dispatch(dispatch),isDone(false),myThread(0) +{ + +} +lightThread::~lightThread() +{ + delete myThread; +} + +void lightThread::run() +{ + while(!isDone) + { + //TODO: get area to process, and then process (by rounds): 1. occlusions, 2.sun, 3.lights(could be difficult, units/items etc...) + {//wait for occlusion (and lights) to be ready + tthread::lock_guard guard(dispatch.occlusionMutex); + if(!dispatch.occlusionReady) + dispatch.occlusionDone.wait(dispatch.occlusionMutex);//wait for work + if(dispatch.unprocessed.size()==0 || !dispatch.occlusionReady) //spurious wake-up + continue; + if(dispatch.occlusion.size()!=canvas.size()) //oh no somebody resized stuff + canvas.resize(dispatch.occlusion.size()); + } + + + { //get my rectangle (any will do) + tthread::lock_guard guard(dispatch.unprocessedMutex); + if (dispatch.unprocessed.size()==0) + { + //wtf?? why?! + continue; + } + myRect=dispatch.unprocessed.top(); + dispatch.unprocessed.pop(); + if (dispatch.unprocessed.size()==0) + { + dispatch.occlusionReady=false; + } + + } + work(); + { + tthread::lock_guard guard(dispatch.writeLock); + combine();//write it back + dispatch.writeCount++; + } + dispatch.writesDone.notify_one();//tell about it to the dispatch. + } +} + +void lightThread::work() +{ + canvas.assign(canvas.size(),rgbf(0,0,0)); + for(int i=myRect.first.x;i0 && !wallhack) + { + power*=v.pow(dt); + } + if(ls.radius>0 && dsq>0) + { + if(power<=ls.power) //quit early if hitting another (stronger) lightsource + return rgbf(); + } + + rgbf oldCol=canvas[tile]; + rgbf ncol=blendMax(power,oldCol); + canvas[tile]=ncol; + + if(wallhack) + return rgbf(); + + + return power; + } + else + return rgbf(); +} +void lightThread::doRay(const rgbf& power,int cx,int cy,int tx,int ty,int num_diffuse) +{ + using namespace std::placeholders; + plotLineDiffuse(cx,cy,tx,ty,power,num_diffuse,std::bind(&lightThread::lightUpCell,this,_1,_2,_3,_4,_5)); +} + +void lightThread::doLight( int x,int y ) +{ + using namespace std::placeholders; + lightSource& csource=dispatch.lights[x*dispatch.getH()+y]; + int num_diffuse=dispatch.num_diffusion; + if(csource.radius>0) + { + rgbf power=csource.power; + int radius =csource.radius; + if(csource.flicker) + { + float flicker=(rand()/(float)RAND_MAX)/2.0f+0.5f; + radius*=flicker; + power=power*flicker; + } + rgbf surrounds; + lightUpCell( power, 0, 0,x, y); //light up the source itself + for(int i=-1;i<2;i++) + for(int j=-1;j<2;j++) + if(i!=0||j!=0) + surrounds += lightUpCell( power, i, j,x+i, y+j); //and this is wall hack (so that walls look nice) + if(surrounds.dot(surrounds)>0.00001f) //if we needed to light up the suroundings, then raycast + { + + plotSquare(x,y,radius, + std::bind(&lightThread::doRay,this,power,x,y,_1,_2,num_diffuse)); + } + } +} +void lightThreadDispatch::signalDoneOcclusion() +{ + { + tthread::lock_guard guardWrite(writeLock); + writeCount=0; + } + tthread::lock_guard guard1(occlusionMutex); + tthread::lock_guard guard2(unprocessedMutex); + while(!unprocessed.empty()) + unprocessed.pop(); + viewPort=getMapViewport(); + int threadCount=threadPool.size(); + int w=viewPort.second.x-viewPort.first.x; + int slicew=w/threadCount; + for(int i=0;ilights),occlusion(parent->ocupancy),num_diffusion(parent->num_diffuse), + lightMap(parent->lightMap),writeCount(0),occlusionReady(false) +{ + +} + +void lightThreadDispatch::shutdown() +{ + for(int i=0;iisDone=true; + + } + occlusionDone.notify_all();//if stuck signal that you are done with stuff. + for(int i=0;imyThread->join(); + } + threadPool.clear(); +} + +int lightThreadDispatch::getW() +{ + return parent->getW(); +} + +int lightThreadDispatch::getH() +{ + return parent->getH(); +} +void threadStub(void * arg) +{ + if(arg) + ((lightThread*)arg)->run(); +} +void lightThreadDispatch::start(int count) +{ + for(int i=0;i nthread(new lightThread(*this)); + nthread->myThread=new tthread::thread(&threadStub,nthread.get()); + threadPool.push_back(std::move(nthread)); + } +} + +void lightThreadDispatch::waitForWrites() +{ + tthread::lock_guard guard(writeLock); + while(threadPool.size()>writeCount)//missed it somehow already. + { + writesDone.wait(writeLock); //if not, wait a bit + } +} + +lightThreadDispatch::~lightThreadDispatch() +{ + shutdown(); +} diff --git a/plugins/rendermax/renderer_light.hpp b/plugins/rendermax/renderer_light.hpp new file mode 100644 index 000000000..357ddaf85 --- /dev/null +++ b/plugins/rendermax/renderer_light.hpp @@ -0,0 +1,377 @@ +#ifndef RENDERER_LIGHT_INCLUDED +#define RENDERER_LIGHT_INCLUDED +#include "renderer_opengl.hpp" +#include "Types.h" +#include +#include +#include +#include +// we are not using boost so let's cheat: +template +inline void hash_combine(std::size_t & seed, const T & v) +{ + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +namespace std +{ + template struct hash> + { + inline size_t operator()(const pair & v) const + { + size_t seed = 0; + ::hash_combine(seed, v.first); + ::hash_combine(seed, v.second); + return seed; + } + }; + template struct hash> + { + inline size_t operator()(const tuple & v) const + { + size_t seed = 0; + ::hash_combine(seed,get<0>(v)); + ::hash_combine(seed,get<1>(v)); + ::hash_combine(seed,get<2>(v)); + return seed; + } + }; +} +// now we can hash pairs and tuples + +#include "modules/MapCache.h" +bool isInRect(const df::coord2d& pos,const DFHack::rect2d& rect); +struct renderer_light : public renderer_wrap { +private: + float light_adaptation; + rgbf adapt_to_light(const rgbf& light) + { + const float influence=0.0001f; + const float max_adapt=1; + const float min_adapt=0; + float intensity=(light.r+light.g+light.b)/3.0; + light_adaptation=intensity*influence+light_adaptation*(1-influence); + float delta=light_adaptation-intensity; + + rgbf ret; + ret.r=light.r-delta; + ret.g=light.g-delta; + ret.b=light.b-delta; + return ret; + //if light_adaptation/intensity~1 then draw 1,1,1 (i.e. totally adapted) + /* + 1. adapted -> 1,1,1 (full bright everything okay) delta=0 multiplier=? + 2. light adapted, real=dark -> darker delta>0 multiplier<1 + 3. dark adapted, real=light -> lighter delta<0 multiplier>1 + */ + //if light_adaptation/intensity!=0 then draw + + } + void colorizeTile(int x,int y) + { + const int tile = x*(df::global::gps->dimy) + y; + old_opengl* p=reinterpret_cast(parent); + float *fg = p->fg + tile * 4 * 6; + float *bg = p->bg + tile * 4 * 6; + float *tex = p->tex + tile * 2 * 6; + rgbf light=lightGrid[tile];//for light adaptation: rgbf light=adapt_to_light(lightGrid[tile]); + + for (int i = 0; i < 6; i++) { //oh how sse would do wonders here, or shaders... + *(fg++) *= light.r; + *(fg++) *= light.g; + *(fg++) *= light.b; + *(fg++) = 1; + + *(bg++) *= light.r; + *(bg++) *= light.g; + *(bg++) *= light.b; + *(bg++) = 1; + } + } + void reinitLightGrid(int w,int h) + { + tthread::lock_guard guard(dataMutex); + lightGrid.resize(w*h,rgbf(1,1,1)); + } + void reinitLightGrid() + { + reinitLightGrid(df::global::gps->dimy,df::global::gps->dimx); + } + +public: + tthread::fast_mutex dataMutex; + std::vector lightGrid; + renderer_light(renderer* parent):renderer_wrap(parent),light_adaptation(1) + { + reinitLightGrid(); + } + virtual void update_tile(int32_t x, int32_t y) { + renderer_wrap::update_tile(x,y); + tthread::lock_guard guard(dataMutex); + colorizeTile(x,y); + }; + virtual void update_all() { + renderer_wrap::update_all(); + tthread::lock_guard guard(dataMutex); + for (int x = 0; x < df::global::gps->dimx; x++) + for (int y = 0; y < df::global::gps->dimy; y++) + colorizeTile(x,y); + }; + virtual void grid_resize(int32_t w, int32_t h) { + renderer_wrap::grid_resize(w,h); + reinitLightGrid(w,h); + }; + virtual void resize(int32_t w, int32_t h) { + renderer_wrap::resize(w,h); + reinitLightGrid(); + } + virtual void set_fullscreen() + { + renderer_wrap::set_fullscreen(); + reinitLightGrid(); + } + virtual void zoom(df::zoom_commands z) + { + renderer_wrap::zoom(z); + reinitLightGrid(); + } +}; +class lightingEngine +{ +public: + lightingEngine(renderer_light* target):myRenderer(target){} + virtual ~lightingEngine(){} + virtual void reinit()=0; + virtual void calculate()=0; + + virtual void updateWindow()=0; + virtual void preRender()=0; + + virtual void loadSettings()=0; + virtual void clear()=0; + + virtual void setHour(float h)=0; + virtual void debug(bool enable)=0; +protected: + renderer_light* myRenderer; +}; +struct lightSource +{ + rgbf power; + int radius; + bool flicker; + lightSource():power(0,0,0),radius(0),flicker(false) + { + + } + lightSource(rgbf power,int radius); + float powerSquared()const + { + return power.r*power.r+power.g*power.g+power.b*power.b; + } + void combine(const lightSource& other); + +}; +struct matLightDef +{ + bool isTransparent; + rgbf transparency; + bool isEmiting; + bool flicker; + rgbf emitColor; + int radius; + matLightDef():isTransparent(false),isEmiting(false),transparency(0,0,0),emitColor(0,0,0),radius(0){} + matLightDef(rgbf transparency,rgbf emit,int rad):isTransparent(true),isEmiting(true), + transparency(transparency),emitColor(emit),radius(rad){} + matLightDef(rgbf emit,int rad):isTransparent(false),isEmiting(true),emitColor(emit),radius(rad),transparency(0,0,0){} + matLightDef(rgbf transparency):isTransparent(true),isEmiting(false),transparency(transparency){} + lightSource makeSource(float size=1) const + { + if(size>0.999 && size<1.001) + return lightSource(emitColor,radius); + else + return lightSource(emitColor*size,radius*size);//todo check if this is sane + } +}; +struct buildingLightDef +{ + matLightDef light; + bool poweredOnly; + bool useMaterial; + float thickness; + float size; + buildingLightDef():poweredOnly(false),useMaterial(true),thickness(1.0f),size(1.0f){} +}; +struct itemLightDef +{ + matLightDef light; + bool haul; + bool equiped; + bool onGround; + bool inBuilding; + bool inContainer; + bool useMaterial; + itemLightDef():haul(true),equiped(true),onGround(true),inBuilding(false),inContainer(false),useMaterial(true){} +}; +struct creatureLightDef +{ + matLightDef light; + +}; +class lightThread; +class lightingEngineViewscreen; +class lightThreadDispatch +{ + lightingEngineViewscreen *parent; +public: + DFHack::rect2d viewPort; + + std::vector > threadPool; + std::vector& lights; + + tthread::mutex occlusionMutex; + tthread::condition_variable occlusionDone; //all threads wait for occlusion to finish + bool occlusionReady; + tthread::mutex unprocessedMutex; + std::stack unprocessed; //stack of parts of map where lighting is not finished + std::vector& occlusion; + int& num_diffusion; + + tthread::mutex writeLock; //mutex for lightMap + std::vector& lightMap; + + tthread::condition_variable writesDone; + int writeCount; + + lightThreadDispatch(lightingEngineViewscreen* p); + ~lightThreadDispatch(); + void signalDoneOcclusion(); + void shutdown(); + void waitForWrites(); + + int getW(); + int getH(); + void start(int count); +}; +class lightThread +{ + std::vector canvas; + lightThreadDispatch& dispatch; + DFHack::rect2d myRect; + void work(); //main light calculation function + void combine(); //combine existing canvas into global lightmap +public: + tthread::thread *myThread; + bool isDone; //no mutex, because bool is atomic + lightThread(lightThreadDispatch& dispatch); + ~lightThread(); + void run(); +private: + void doLight(int x,int y); + void doRay(const rgbf& power,int cx,int cy,int tx,int ty,int num_diffuse); + rgbf lightUpCell(rgbf power,int dx,int dy,int tx,int ty); +}; +class lightingEngineViewscreen:public lightingEngine +{ +public: + lightingEngineViewscreen(renderer_light* target); + ~lightingEngineViewscreen(); + void reinit(); + void calculate(); + + void updateWindow(); + void preRender(); + void loadSettings(); + void clear(); + + void debug(bool enable){doDebug=enable;}; +private: + void fixAdvMode(int mode); + df::coord2d worldToViewportCoord(const df::coord2d& in,const DFHack::rect2d& r,const df::coord2d& window2d) ; + + + void doSun(const lightSource& sky,MapExtras::MapCache& map); + void doOcupancyAndLights(); + rgbf propogateSun(MapExtras::Block* b, int x,int y,const rgbf& in,bool lastLevel); + void doRay(std::vector & target, rgbf power,int cx,int cy,int tx,int ty); + void doFovs(); + void doLight(std::vector & target, int index); + rgbf lightUpCell(std::vector & target, rgbf power,int dx,int dy,int tx,int ty); + bool addLight(int tileId,const lightSource& light); + void addOclusion(int tileId,const rgbf& c,float thickness); + + matLightDef* getMaterialDef(int matType,int matIndex); + buildingLightDef* getBuildingDef(df::building* bld); + creatureLightDef* getCreatureDef(df::unit* u); + itemLightDef* getItemDef(df::item* it); + + //apply material to cell + void applyMaterial(int tileId,const matLightDef& mat,float size=1, float thickness = 1); + //try to find and apply material, if failed return false, and if def!=null then apply def. + bool applyMaterial(int tileId,int matType,int matIndex,float size=1,float thickness = 1,const matLightDef* def=NULL); + + size_t inline getIndex(int x,int y) + { + return x*h+y; + } + df::coord2d inline getCoords(int index) + { + return df::coord2d(index/h, index%h); + } + //maps + std::vector lightMap; + std::vector ocupancy; + std::vector lights; + + //Threading stuff + int num_diffuse; //under same lock as ocupancy + lightThreadDispatch threading; + //misc + void setHour(float h){dayHour=h;}; + + int getW()const {return w;} + int getH()const {return h;} +public: + void lightWorkerThread(void * arg); +private: + rgbf getSkyColor(float v); + bool doDebug; + + //settings + float daySpeed; + float dayHour; //<0 to cycle + std::vector dayColors; // a gradient of colors, first to 0, last to 24 + ///set up sane settings if setting file does not exist. + void defaultSettings(); + + static int parseMaterials(lua_State* L); + static int parseSpecial(lua_State* L); + static int parseBuildings(lua_State* L); + static int parseItems(lua_State* L); + static int parseCreatures(lua_State* L); + //special stuff + matLightDef matLava; + matLightDef matIce; + matLightDef matAmbience; + matLightDef matCursor; + matLightDef matWall; + matLightDef matWater; + matLightDef matCitizen; + float levelDim; + int adv_mode; + //materials + std::unordered_map,matLightDef> matDefs; + //buildings + std::unordered_map,buildingLightDef> buildingDefs; + //creatures + std::unordered_map,creatureLightDef> creatureDefs; + //items + std::unordered_map,itemLightDef> itemDefs; + int w,h; + DFHack::rect2d mapPort; + friend class lightThreadDispatch; +}; +rgbf blend(const rgbf& a,const rgbf& b); +rgbf blendMax(const rgbf& a,const rgbf& b); +#endif diff --git a/plugins/rendermax/renderer_opengl.hpp b/plugins/rendermax/renderer_opengl.hpp new file mode 100644 index 000000000..085bdf48a --- /dev/null +++ b/plugins/rendermax/renderer_opengl.hpp @@ -0,0 +1,417 @@ +//original file from https://github.com/Baughn/Dwarf-Fortress--libgraphics- +#ifndef RENDERER_OPENGL_INCLUDED +#define RENDERER_OPENGL_INCLUDED + +#include "tinythread.h" +#include "fast_mutex.h" + +#include "Core.h" +#include +#include "df/renderer.h" +#include "df/init.h" +#include "df/enabler.h" +#include "df/zoom_commands.h" +#include "df/texture_handler.h" +#include "df/graphic.h" +#include +#include + +using df::renderer; +using df::init; +using df::enabler; + +struct old_opengl:public renderer +{ + void* sdlSurface; + int32_t dispx,dispy; + float *vertexes, *fg, *bg, *tex; + int32_t zoom_steps,forced_steps,natural_w,natural_h; + int32_t off_x,off_y,size_x,size_y; +}; +struct renderer_wrap : public renderer { +private: + void set_to_null() { + screen = NULL; + screentexpos = NULL; + screentexpos_addcolor = NULL; + screentexpos_grayscale = NULL; + screentexpos_cf = NULL; + screentexpos_cbr = NULL; + screen_old = NULL; + screentexpos_old = NULL; + screentexpos_addcolor_old = NULL; + screentexpos_grayscale_old = NULL; + screentexpos_cf_old = NULL; + screentexpos_cbr_old = NULL; + } + + void copy_from_inner() { + screen = parent->screen; + screentexpos = parent->screentexpos; + screentexpos_addcolor = parent->screentexpos_addcolor; + screentexpos_grayscale = parent->screentexpos_grayscale; + screentexpos_cf = parent->screentexpos_cf; + screentexpos_cbr = parent->screentexpos_cbr; + screen_old = parent->screen_old; + screentexpos_old = parent->screentexpos_old; + screentexpos_addcolor_old = parent->screentexpos_addcolor_old; + screentexpos_grayscale_old = parent->screentexpos_grayscale_old; + screentexpos_cf_old = parent->screentexpos_cf_old; + screentexpos_cbr_old = parent->screentexpos_cbr_old; + } + + void copy_to_inner() { + parent->screen = screen; + parent->screentexpos = screentexpos; + parent->screentexpos_addcolor = screentexpos_addcolor; + parent->screentexpos_grayscale = screentexpos_grayscale; + parent->screentexpos_cf = screentexpos_cf; + parent->screentexpos_cbr = screentexpos_cbr; + parent->screen_old = screen_old; + parent->screentexpos_old = screentexpos_old; + parent->screentexpos_addcolor_old = screentexpos_addcolor_old; + parent->screentexpos_grayscale_old = screentexpos_grayscale_old; + parent->screentexpos_cf_old = screentexpos_cf_old; + parent->screentexpos_cbr_old = screentexpos_cbr_old; + } +public: + renderer_wrap(renderer* parent):parent(parent) + { + copy_from_inner(); + } + virtual void update_tile(int32_t x, int32_t y) { + + copy_to_inner(); + parent->update_tile(x,y); + }; + virtual void update_all() { + copy_to_inner(); + parent->update_all(); + }; + virtual void render() { + copy_to_inner(); + parent->render(); + }; + virtual void set_fullscreen() { + copy_to_inner(); + parent->set_fullscreen(); + copy_from_inner(); + }; + virtual void zoom(df::zoom_commands z) { + copy_to_inner(); + parent->zoom(z); + copy_from_inner(); + }; + virtual void resize(int32_t w, int32_t h) { + copy_to_inner(); + parent->resize(w,h); + copy_from_inner(); + }; + virtual void grid_resize(int32_t w, int32_t h) { + copy_to_inner(); + parent->grid_resize(w,h); + copy_from_inner(); + }; + virtual ~renderer_wrap() { + df::global::enabler->renderer=parent; + }; + virtual bool get_mouse_coords(int32_t* x, int32_t* y) { + return parent->get_mouse_coords(x,y); + }; + virtual bool uses_opengl() { + return parent->uses_opengl(); + }; + void invalidateRect(int32_t x,int32_t y,int32_t w,int32_t h) + { + for(int i=x;idimy + j; + screen_old[index*4]=screen[index*4]+1;//ensure tile is different + } + }; + void invalidate() + { + invalidateRect(0,0,df::global::gps->dimx,df::global::gps->dimy); + //df::global::gps->force_full_display_count++; + }; +protected: + renderer* parent; +}; +struct renderer_trippy : public renderer_wrap { +private: + float rFloat() + { + return rand()/(float)RAND_MAX; + } + void colorizeTile(int x,int y) + { + const int tile = x*(df::global::gps->dimy) + y; + old_opengl* p=reinterpret_cast(parent); + float *fg = p->fg + tile * 4 * 6; + float *bg = p->bg + tile * 4 * 6; + float *tex = p->tex + tile * 2 * 6; + const float val=1/2.0; + + float r=rFloat()*val - val/2; + float g=rFloat()*val - val/2; + float b=rFloat()*val - val/2; + + float backr=rFloat()*val - val/2; + float backg=rFloat()*val - val/2; + float backb=rFloat()*val - val/2; + for (int i = 0; i < 6; i++) { + *(fg++) += r; + *(fg++) += g; + *(fg++) += b; + *(fg++) = 1; + + *(bg++) += backr; + *(bg++) += backg; + *(bg++) += backb; + *(bg++) = 1; + } + } +public: + renderer_trippy(renderer* parent):renderer_wrap(parent) + { + } + virtual void update_tile(int32_t x, int32_t y) { + renderer_wrap::update_tile(x,y); + colorizeTile(x,y); + }; + virtual void update_all() { + renderer_wrap::update_all(); + for (int x = 0; x < df::global::gps->dimx; x++) + for (int y = 0; y < df::global::gps->dimy; y++) + colorizeTile(x,y); + }; +}; + +struct rgbf +{ + float r,g,b; + rgbf():r(0),g(0),b(0) + { + + } + rgbf(float r,float g,float b):r(r),g(g),b(b) + { + + } + rgbf operator-(const rgbf& cell) const + { + return rgbf(r-cell.r,g-cell.g,b-cell.b); + } + rgbf operator*(float val)const + { + return rgbf(r*val,g*val,b*val); + } + rgbf operator/(float val) const + { + return rgbf(r/val,g/val,b/val); + } + rgbf operator*(const rgbf& cell) const + { + return rgbf(r*cell.r,g*cell.g,b*cell.b); + } + rgbf operator*=(float val) + { + r*=val; + g*=val; + b*=val; + return *this; + } + rgbf operator*=(const rgbf& cell) + { + r*=cell.r; + g*=cell.g; + b*=cell.b; + return *this; + } + rgbf operator+=(const rgbf& cell) + { + r+=cell.r; + g+=cell.g; + b+=cell.b; + return *this; + } + rgbf operator+(const rgbf& other) const + { + return rgbf(r+other.r,g+other.g,b+other.b); + } + bool operator<=(const rgbf& other) const + { + return r<=other.r && g<=other.g && b<=other.b; + } + float dot(const rgbf& other) const + { + return r*other.r+g*other.g+b*other.b; + } + rgbf pow(const float exp) const + { + return rgbf(std::pow(r, exp), std::pow(g, exp), std::pow(b, exp)); + } + rgbf pow(const int exp) const + { + return rgbf(std::pow(r, exp), std::pow(g, exp), std::pow(b, exp)); + } +}; +struct renderer_test : public renderer_wrap { +private: + void colorizeTile(int x,int y) + { + const int tile = x*(df::global::gps->dimy) + y; + old_opengl* p=reinterpret_cast(parent); + float *fg = p->fg + tile * 4 * 6; + float *bg = p->bg + tile * 4 * 6; + float *tex = p->tex + tile * 2 * 6; + rgbf light=lightGrid[tile]; + for (int i = 0; i < 6; i++) { + *(fg++) *= light.r; + *(fg++) *= light.g; + *(fg++) *= light.b; + *(fg++) = 1; + + *(bg++) *= light.r; + *(bg++) *= light.g; + *(bg++) *= light.b; + *(bg++) = 1; + } + } + void reinitLightGrid(int w,int h) + { + tthread::lock_guard guard(dataMutex); + lightGrid.resize(w*h); + } + void reinitLightGrid() + { + reinitLightGrid(df::global::gps->dimy,df::global::gps->dimx); + } +public: + tthread::fast_mutex dataMutex; + std::vector lightGrid; + renderer_test(renderer* parent):renderer_wrap(parent) + { + reinitLightGrid(); + } + virtual void update_tile(int32_t x, int32_t y) { + renderer_wrap::update_tile(x,y); + tthread::lock_guard guard(dataMutex); + colorizeTile(x,y); + //some sort of mutex or sth? + //and then map read + }; + virtual void update_all() { + renderer_wrap::update_all(); + tthread::lock_guard guard(dataMutex); + for (int x = 0; x < df::global::gps->dimx; x++) + for (int y = 0; y < df::global::gps->dimy; y++) + colorizeTile(x,y); + //some sort of mutex or sth? + //and then map read + //same stuff for all of them i guess... + }; + virtual void set_fullscreen() + { + renderer_wrap::set_fullscreen(); + reinitLightGrid(); + } + virtual void zoom(df::zoom_commands z) + { + renderer_wrap::zoom(z); + reinitLightGrid(); + } + virtual void grid_resize(int32_t w, int32_t h) { + renderer_wrap::grid_resize(w,h); + reinitLightGrid(w,h); + }; + virtual void resize(int32_t w, int32_t h) { + renderer_wrap::resize(w,h); + reinitLightGrid(w,h); + } +}; + +struct rgba +{ + float r,g,b,a; +}; +struct renderer_lua : public renderer_wrap { +private: + void overwriteTile(int x,int y) + { + const int tile = xyToTile(x,y); + old_opengl* p=reinterpret_cast(parent); + float *fg = p->fg + tile * 4 * 6; + float *bg = p->bg + tile * 4 * 6; + float *tex = p->tex + tile * 2 * 6; + rgbf fm=foreMult[tile]; + rgbf fo=foreOffset[tile]; + + rgbf bm=backMult[tile]; + rgbf bo=backOffset[tile]; + for (int i = 0; i < 6; i++) { + rgba* fore=reinterpret_cast(fg); + fore->r=fore->r*fm.r+fo.r; + fore->g=fore->g*fm.g+fo.g; + fore->b=fore->b*fm.b+fo.b; + + fg+=4; + rgba* back=reinterpret_cast(bg); + back->r=back->r*bm.r+bo.r; + back->g=back->g*bm.g+bo.g; + back->b=back->b*bm.b+bo.b; + bg+=4; + } + } + void reinitGrids(int w,int h) + { + tthread::lock_guard guard(dataMutex); + foreOffset.resize(w*h); + foreMult.resize(w*h); + backOffset.resize(w*h); + backMult.resize(w*h); + } + void reinitGrids() + { + reinitGrids(df::global::gps->dimy,df::global::gps->dimx); + } +public: + tthread::fast_mutex dataMutex; + std::vector foreOffset,foreMult; + std::vector backOffset,backMult; + inline int xyToTile(int x, int y) + { + return x*(df::global::gps->dimy) + y; + } + renderer_lua(renderer* parent):renderer_wrap(parent) + { + reinitGrids(); + } + virtual void update_tile(int32_t x, int32_t y) { + renderer_wrap::update_tile(x,y); + tthread::lock_guard guard(dataMutex); + overwriteTile(x,y); + //some sort of mutex or sth? + //and then map read + }; + virtual void update_all() { + renderer_wrap::update_all(); + tthread::lock_guard guard(dataMutex); + for (int x = 0; x < df::global::gps->dimx; x++) + for (int y = 0; y < df::global::gps->dimy; y++) + overwriteTile(x,y); + //some sort of mutex or sth? + //and then map read + //same stuff for all of them i guess... + }; + virtual void grid_resize(int32_t w, int32_t h) { + renderer_wrap::grid_resize(w,h); + reinitGrids(w,h); + }; + virtual void resize(int32_t w, int32_t h) { + renderer_wrap::resize(w,h); + reinitGrids(w,h); + } +}; +#endif \ No newline at end of file diff --git a/plugins/rendermax/rendermax.cpp b/plugins/rendermax/rendermax.cpp new file mode 100644 index 000000000..dcfee0092 --- /dev/null +++ b/plugins/rendermax/rendermax.cpp @@ -0,0 +1,457 @@ +#include +#include + +#include + +#include + +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include +#include "df/renderer.h" +#include "df/enabler.h" + +#include "renderer_opengl.hpp" +#include "renderer_light.hpp" + +#include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_dungeonmodest.h" + +#include + +using df::viewscreen_dungeonmodest; +using df::viewscreen_dwarfmodest; + +using namespace DFHack; +using std::vector; +using std::string; +enum RENDERER_MODE +{ + MODE_DEFAULT,MODE_TRIPPY,MODE_TRUECOLOR,MODE_LUA,MODE_LIGHT +}; +RENDERER_MODE current_mode=MODE_DEFAULT; +lightingEngine *engine=NULL; + +static command_result rendermax(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("rendermax"); + + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "rendermax", "switch rendering engine.", rendermax, false, + " rendermax trippy\n" + " rendermax truecolor red|green|blue|white\n" + " rendermax lua\n" + " rendermax light - lighting engine\n" + " rendermax light reload - reload the settings file\n" + " rendermax light sun |cycle - set time to x (in hours) or cycle (same effect if x<0)\n" + " rendermax light occlusionON|occlusionOFF - debug the occlusion map\n" + " rendermax disable\n" + )); + return CR_OK; +} + +struct dwarmode_render_hook : viewscreen_dwarfmodest{ + typedef df::viewscreen_dwarfmodest interpose_base; + DEFINE_VMETHOD_INTERPOSE(void,render,()) + { + CoreSuspendClaimer suspend; + engine->preRender(); + INTERPOSE_NEXT(render)(); + engine->calculate(); + engine->updateWindow(); + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(dwarmode_render_hook, render); + +struct dungeon_render_hook : viewscreen_dungeonmodest{ + typedef df::viewscreen_dungeonmodest interpose_base; + DEFINE_VMETHOD_INTERPOSE(void,render,()) + { + CoreSuspendClaimer suspend; + engine->preRender(); + INTERPOSE_NEXT(render)(); + engine->calculate(); + engine->updateWindow(); + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(dungeon_render_hook, render); + +void removeOld() +{ + CoreSuspender lock; + if(engine) + { + INTERPOSE_HOOK(dwarmode_render_hook,render).apply(false); + INTERPOSE_HOOK(dungeon_render_hook,render).apply(false); + delete engine; + engine=0; + } + if(current_mode!=MODE_DEFAULT) + delete df::global::enabler->renderer; + + current_mode=MODE_DEFAULT; +} +void installNew(df::renderer* r,RENDERER_MODE newMode) +{ + df::global::enabler->renderer=r; + current_mode=newMode; +} +static void lockGrids() +{ + if(current_mode!=MODE_LUA) + return ; + renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + r->dataMutex.lock(); +} +static void unlockGrids() +{ + if(current_mode!=MODE_LUA) + return ; + renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + r->dataMutex.unlock(); +} +static void resetGrids() +{ + if(current_mode!=MODE_LUA) + return ; + renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + for(size_t i=0;iforeMult.size();i++) + { + r->foreMult[i]=rgbf(1,1,1); + r->foreOffset[i]=rgbf(0,0,0); + r->backMult[i]=rgbf(1,1,1); + r->backOffset[i]=rgbf(0,0,0); + } +} +static int getGridsSize(lua_State* L) +{ + if(current_mode!=MODE_LUA) + return -1; + renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + lua_pushnumber(L,df::global::gps->dimx); + lua_pushnumber(L,df::global::gps->dimy); + return 2; +} +static int getCell(lua_State* L) +{ + if(current_mode!=MODE_LUA) + return 0; + renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + int x=luaL_checknumber(L,1); + int y=luaL_checknumber(L,2); + int id=r->xyToTile(x,y); + rgbf fo=r->foreOffset[id]; + rgbf fm=r->foreMult[id]; + rgbf bo=r->backOffset[id]; + rgbf bm=r->backMult[id]; + lua_newtable(L); + + lua_newtable(L); + lua_pushnumber(L,fo.r); + lua_setfield(L,-2,"r"); + lua_pushnumber(L,fo.g); + lua_setfield(L,-2,"g"); + lua_pushnumber(L,fo.b); + lua_setfield(L,-2,"b"); + lua_setfield(L,-2,"fo"); + + lua_newtable(L); + lua_pushnumber(L,fm.r); + lua_setfield(L,-2,"r"); + lua_pushnumber(L,fm.g); + lua_setfield(L,-2,"g"); + lua_pushnumber(L,fm.b); + lua_setfield(L,-2,"b"); + lua_setfield(L,-2,"fm"); + + lua_newtable(L); + lua_pushnumber(L,bo.r); + lua_setfield(L,-2,"r"); + lua_pushnumber(L,bo.g); + lua_setfield(L,-2,"g"); + lua_pushnumber(L,bo.b); + lua_setfield(L,-2,"b"); + lua_setfield(L,-2,"bo"); + + lua_newtable(L); + lua_pushnumber(L,bm.r); + lua_setfield(L,-2,"r"); + lua_pushnumber(L,bm.g); + lua_setfield(L,-2,"g"); + lua_pushnumber(L,bm.b); + lua_setfield(L,-2,"b"); + lua_setfield(L,-2,"bm"); + return 1; +} +static int setCell(lua_State* L) +{ + if(current_mode!=MODE_LUA) + return 0; + renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + int x=luaL_checknumber(L,1); + int y=luaL_checknumber(L,2); + + rgbf fo; + lua_getfield(L,3,"fo"); + lua_getfield(L,-1,"r"); + fo.r=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,-1,"g"); + fo.g=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,-1,"b"); + fo.b=lua_tonumber(L,-1);lua_pop(L,1); + rgbf fm; + lua_getfield(L,3,"fm"); + lua_getfield(L,-1,"r"); + fm.r=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,-1,"g"); + fm.g=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,-1,"b"); + fm.b=lua_tonumber(L,-1);lua_pop(L,1); + + rgbf bo; + lua_getfield(L,3,"bo"); + lua_getfield(L,-1,"r"); + bo.r=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,-1,"g"); + bo.g=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,-1,"b"); + bo.b=lua_tonumber(L,-1);lua_pop(L,1); + + rgbf bm; + lua_getfield(L,3,"bm"); + lua_getfield(L,-1,"r"); + bm.r=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,-1,"g"); + bm.g=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,-1,"b"); + bm.b=lua_tonumber(L,-1);lua_pop(L,1); + int id=r->xyToTile(x,y); + r->foreMult[id]=fm; + r->foreOffset[id]=fo; + r->backMult[id]=bm; + r->backOffset[id]=bo; + return 0; +} +static int invalidate(lua_State* L) +{ + if(current_mode!=MODE_LUA) + return 0; + renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + if(lua_gettop(L)==0) + { + r->invalidate(); + } + else + { + int x,y,w,h; + lua_getfield(L,1,"x"); + x=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,1,"y"); + y=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,1,"w"); + w=lua_tonumber(L,-1);lua_pop(L,1); + lua_getfield(L,1,"h"); + h=lua_tonumber(L,-1);lua_pop(L,1); + r->invalidateRect(x,y,w,h); + } + return 0; +} +bool isEnabled() +{ + return current_mode==MODE_LUA; +} +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(isEnabled), + DFHACK_LUA_FUNCTION(lockGrids), + DFHACK_LUA_FUNCTION(unlockGrids), + DFHACK_LUA_FUNCTION(resetGrids), + DFHACK_LUA_END +}; +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(getCell), + DFHACK_LUA_COMMAND(setCell), + DFHACK_LUA_COMMAND(getGridsSize), + DFHACK_LUA_COMMAND(invalidate), + DFHACK_LUA_END +}; + +static void enable_hooks(bool enable) +{ + INTERPOSE_HOOK(dwarmode_render_hook,render).apply(enable); + INTERPOSE_HOOK(dungeon_render_hook,render).apply(enable); + if(enable && engine) + { + engine->loadSettings(); + } +} + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + if(current_mode!=MODE_LIGHT) + return CR_OK; + switch(event) + { + case SC_VIEWSCREEN_CHANGED: + { + CoreSuspendClaimer suspender; + if(current_mode==MODE_LIGHT) + { + engine->clear(); + } + } + break; + case SC_WORLD_LOADED: + enable_hooks(true); + break; + case SC_WORLD_UNLOADED: + enable_hooks(false); + break; + default: + break; + } + return CR_OK; +} + + +static command_result rendermax(color_ostream &out, vector & parameters) +{ + if(parameters.size()==0) + return CR_WRONG_USAGE; + if(!df::global::enabler->renderer->uses_opengl()) + { + out.printerr("Sorry, this plugin needs open gl enabled printmode. Try STANDARD or other non-2d"); + return CR_FAILURE; + } + string cmd=parameters[0]; + if(cmd=="trippy") + { + removeOld(); + installNew(new renderer_trippy(df::global::enabler->renderer),MODE_TRIPPY); + return CR_OK; + } + else if(cmd=="truecolor") + { + if(current_mode!=MODE_TRUECOLOR) + { + removeOld(); + installNew(new renderer_test(df::global::enabler->renderer),MODE_TRUECOLOR); + } + if(current_mode==MODE_TRUECOLOR && parameters.size()==2) + { + rgbf red(1,0,0),green(0,1,0),blue(0,0,1),white(1,1,1); + rgbf cur=white; + rgbf dim(0.2f,0.2f,0.2f); + string col=parameters[1]; + if(col=="red") + cur=red; + else if(col=="green") + cur=green; + else if(col=="blue") + cur=blue; + + renderer_test* r=reinterpret_cast(df::global::enabler->renderer); + tthread::lock_guard guard(r->dataMutex); + int h=df::global::gps->dimy; + int w=df::global::gps->dimx; + int cx=w/2; + int cy=h/2; + int rad=cx; + if(rad>cy)rad=cy; + rad/=2; + int radsq=rad*rad; + for(size_t i=0;ilightGrid.size();i++) + { + r->lightGrid[i]=dim; + } + for(int i=-rad;ilightGrid[(cx+i)*h+(cy+j)]=dim+cur*val; + } + } + return CR_OK; + } + } + else if(cmd=="lua") + { + removeOld(); + installNew(new renderer_lua(df::global::enabler->renderer),MODE_LUA); + lockGrids(); + resetGrids(); + unlockGrids(); + return CR_OK; + } + else if(cmd=="light") + { + if(current_mode!=MODE_LIGHT) + { + removeOld(); + renderer_light *myRender=new renderer_light(df::global::enabler->renderer); + installNew(myRender,MODE_LIGHT); + engine=new lightingEngineViewscreen(myRender); + + if (Core::getInstance().isWorldLoaded()) + plugin_onstatechange(out, SC_WORLD_LOADED); + } + else if(current_mode==MODE_LIGHT && parameters.size()>1) + { + if(parameters[1]=="reload") + { + enable_hooks(true); + } + else if(parameters[1]=="sun" && parameters.size()==3) + { + if(parameters[2]=="cycle") + { + engine->setHour(-1); + } + else + { + std::stringstream ss; + ss<>h; + engine->setHour(h); + } + } + else if(parameters[1]=="occlusionON") + { + engine->debug(true); + }else if(parameters[1]=="occlusionOFF") + { + engine->debug(false); + } + } + else + out.printerr("Light mode already enabled"); + + return CR_OK; + } + else if(cmd=="disable") + { + if(current_mode==MODE_DEFAULT) + out.print("%s\n","Not installed, doing nothing."); + else + removeOld(); + CoreSuspender guard; + df::global::gps->force_full_display_count++; + return CR_OK; + } + return CR_WRONG_USAGE; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &) +{ + removeOld(); + return CR_OK; +} diff --git a/plugins/rendermax/rendermax.lua b/plugins/rendermax/rendermax.lua new file mode 100644 index 000000000..d26b3d2c1 --- /dev/null +++ b/plugins/rendermax/rendermax.lua @@ -0,0 +1,225 @@ +--scroll down to the end for configuration +ret={...} +ret=ret[1] +ret.materials={} +ret.buildings={} +ret.special={} +ret.items={} +ret.creatures={} +for k,v in pairs(ret) do + _ENV[k]=v +end +-- add material by id (index,mat pair or token string or a type number), flags is a table of strings +-- supported flags (but not implemented): +-- flicker +function addMaterial(id,transparency,emitance,radius,flags) + local matinfo + if type(id)=="string" then + matinfo=dfhack.matinfo.find(id) + elseif type(id)=="table" then + matinfo=dfhack.matinfo.decode(id[1],id[2]) + else + matinfo=dfhack.matinfo.decode(id,0) + end + if matinfo==nil then + error("Material not found") + end + materials[matinfo.type]=materials[matinfo.type] or {} + materials[matinfo.type][matinfo.index]=makeMaterialDef(transparency,emitance,radius,flags) +end +function buildingLookUp(id) + local tokens={} + local lookup={ Workshop=df.workshop_type,Furnace=df.furnace_type,Trap=df.trap_type, + SiegeEngine=df.siegeengine_type} + for i in string.gmatch(id, "[^:]+") do + table.insert(tokens,i) + end + local ret={} + ret.type=df.building_type[tokens[1]] + if tokens[2] then + local type_array=lookup[tokens[1]] + if type_array then + ret.subtype=type_array[tokens[2]] + end + if tokens[2]=="Custom" and tokens[3] then --TODO cache for faster lookup + if ret.type==df.building_type.Workshop then + for k,v in pairs(df.global.world.raws.buildings.workshops) do + if v.code==tokens[3] then + ret.custom=v.id + return ret + end + end + elseif ret.type==df.building_type.Furnace then + for k,v in pairs(df.global.world.raws.buildings.furnaces) do + if v.code==tokens[3] then + ret.custom=v.id + return ret + end + end + end + end + qerror("Invalid custom building:"..tokens[3]) + end + return ret +end +function itemLookup(id) + local ret={} + local tokens={} + for i in string.gmatch(id, "[^:]+") do + table.insert(tokens,i) + end + ret.type=df.item_type[tokens[1]] + ret.subtype=-1 + if tokens[2] then + for k,v in ipairs(df.global.world.raws.itemdefs.all) do --todo lookup correct itemdef + if v.id==tokens[2] then + ret.subtype=v.subtype + return ret + end + end + qerror("Failed item subtype lookup:"..tokens[2]) + end + return ret +end +function creatureLookup(id) + local ret={} + local tokens={} + for i in string.gmatch(id, "[^:]+") do + table.insert(tokens,i) + end + for k,v in ipairs(df.global.world.raws.creatures.all) do + if v.creature_id==tokens[1] then + ret.type=k + if tokens[2] then + for k,v in ipairs(v.caste) do + if v.caste_id==tokens[2] then + ret.subtype=k + break + end + end + if ret.subtype==nil then + qerror("caste "..tokens[2].." for "..tokens[1].." not found") + end + end + return ret + end + end + qerror("Failed to find race:"..tokens[1]) +end +-- add creature by id ("DWARF" or "DWARF:MALE") +-- supported flags: +function addCreature(id,transparency,emitance,radius,flags) + local crId=creatureLookup(id) + local mat=makeMaterialDef(transparency,emitance,radius,flags) + table.insert(creatures,{race=crId.type,caste=crId.subtype or -1, light=mat}) +end +-- add item by id ( "TOTEM" or "WEAPON:PICK" or "WEAPON" for all the weapon types) +-- supported flags: +-- hauling --active when hauled TODO::currently all mean same thing... +-- equiped --active while equiped TODO::currently all mean same thing... +-- inBuilding --active in building TODO::currently all mean same thing... +-- contained --active in container TODO::currently all mean same thing... +-- onGround --active on ground +-- useMaterial --uses material, but the defined things overwrite +function addItem(id,transparency,emitance,radius,flags) + local itemId=itemLookup(id) + local mat=makeMaterialDef(transparency,emitance,radius,flags) + table.insert(items,{["type"]=itemId.type,subtype=itemId.subtype,light=mat}) +end +-- add building by id (string e.g. "Statue" or "Workshop:Masons", flags is a table of strings +-- supported flags: +-- useMaterial --uses material, but the defined things overwrite +-- poweredOnly --glow only when powered +function addBuilding(id,transparency,emitance,radius,flags,size,thickness) + size=size or 1 + thickness=thickness or 1 + local bld=buildingLookUp(id) + local mat=makeMaterialDef(transparency,emitance,radius,flags) + mat.size=size + mat.thickness=thickness + buildings[bld.type]=buildings[bld.type] or {} + if bld.subtype then + if bld.custom then + buildings[bld.type][bld.subtype]=buildings[bld.type][bld.subtype] or {} + buildings[bld.type][bld.subtype][bld.custom]=mat + else + buildings[bld.type][bld.subtype]={[-1]=mat} + end + else + buildings[bld.type][-1]={[-1]=mat} + end +end +function makeMaterialDef(transparency,emitance,radius,flags) + local flg + if flags then + flg={} + for k,v in ipairs(flags) do + flg[v]=true + end + end + return {tr=transparency,em=emitance,rad=radius,flags=flg} +end +function colorFrom16(col16) + local col=df.global.enabler.ccolor[col16] + return {col[0],col[1],col[2]} +end +function addGems() + for k,v in pairs(df.global.world.raws.inorganics) do + if v.material.flags.IS_GEM then + addMaterial("INORGANIC:"..v.id,colorFrom16(v.material.tile_color[0]+v.material.tile_color[2]*8)) + end + end +end +------------------------------------------------------------------------ +---------------- Configuration Starts Here ------------------------- +------------------------------------------------------------------------ +is_computer_quantum=false -- will enable more costly parts in the future +--special things +special.LAVA=makeMaterialDef({0.8,0.2,0.2},{0.8,0.2,0.2},5) +special.WATER=makeMaterialDef({0.5,0.5,0.8}) +special.FROZEN_LIQUID=makeMaterialDef({0.2,0.7,0.9}) -- ice +special.AMBIENT=makeMaterialDef({0.85,0.85,0.85}) --ambient fog +special.CURSOR=makeMaterialDef({1,1,1},{0.96,0.84,0.03},11, {"flicker"}) +special.CITIZEN=makeMaterialDef(nil,{0.80,0.80,0.90},6) +special.LevelDim=0.2 -- darkness. Do not set to 0 +special.dayHour=-1 -- <0 cycle, else hour of the day +special.dayColors={ {0,0,0}, --dark at 0 hours + {0.6,0.5,0.5}, --reddish twilight + {1,1,1}, --fullbright at 12 hours + {0.5,0.5,0.5}, + {0,0,0}} --dark at 24 hours +special.daySpeed=1 -- 1->1200 cur_year_ticks per day. 2->600 ticks +special.diffusionCount=1 -- split beam max 1 times to mimic diffuse lights +special.advMode=0 -- 1 or 0 different modes for adv mode. 0-> use df vision system, + -- 1(does not work)->everything visible, let rendermax light do the work +--TODO dragonfire +--materials + + +-- glasses +addMaterial("GLASS_GREEN",{0.1,0.9,0.5}) +addMaterial("GLASS_CLEAR",{0.5,0.95,0.9}) +addMaterial("GLASS_CRYSTAL",{0.75,0.95,0.95}) +-- Plants +addMaterial("PLANT:TOWER_CAP",nil,{0.65,0.65,0.65},6) +addMaterial("PLANT:MUSHROOM_CUP_DIMPLE",nil,{0.03,0.03,0.5},3) +addMaterial("PLANT:CAVE MOSS",nil,{0.1,0.1,0.4},2) +addMaterial("PLANT:MUSHROOM_HELMET_PLUMP",nil,{0.2,0.1,0.6},2) +-- inorganics +addMaterial("INORGANIC:ADAMANTINE",{0.1,0.3,0.3},{0.1,0.3,0.3},4) +-- creature stuff +addMaterial("CREATURE:DRAGON:BLOOD",nil,{0.6,0.1,0.1},4) +addGems() +--buildings +addBuilding("Statue",{1,1,1},{0.9,0.75,0.3},8) +addBuilding("Bed",{1,1,1},{0.3,0.2,0.0},2) +addBuilding("WindowGlass",nil,nil,0,{"useMaterial"}) +addBuilding("WindowGem",nil,nil,0,{"useMaterial"}) +addBuilding("Door",nil,nil,0,{"useMaterial"}) -- special case, only closed door obstruct/emit light +addBuilding("Floodgate",nil,nil,0,{"useMaterial"}) -- special case, only closed door obstruct/emit light +--creatures +addCreature("ELEMENTMAN_MAGMA",{0.8,0.2,0.2},{0.8,0.2,0.2},5) +--items +addItem("GEM",nil,nil,{"useMaterial","onGround"}) +addItem("ROUGH",nil,nil,{"useMaterial","onGround"}) +addItem("SMALLGEM",nil,nil,{"useMaterial","onGround"}) diff --git a/plugins/skeleton/CMakeLists.txt b/plugins/skeleton/CMakeLists.txt index 542f08fe3..794f4a5d8 100644 --- a/plugins/skeleton/CMakeLists.txt +++ b/plugins/skeleton/CMakeLists.txt @@ -24,7 +24,7 @@ IF(UNIX) # windows ELSE(UNIX) SET(PROJECT_LIBS - # add any extra linux libs here + # add any extra windows libs here ${PROJECT_LIBS} $(NOINHERIT) ) diff --git a/scripts/devel/light.lua b/scripts/devel/light.lua new file mode 100644 index 000000000..48324d797 --- /dev/null +++ b/scripts/devel/light.lua @@ -0,0 +1,377 @@ +-- an experimental lighting engine for df. param: "static" to not recalc when in game. press "~" to recalculate. "`" to exit +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local render = require 'plugins.rendermax' + +local levelDim=0.05 +local tile_attrs = df.tiletype.attrs + +local args={...} + +function setCell(x,y,cell) + cell=cell or {} + cell.fm=cell.fm or {r=1,g=1,b=1} + cell.bm=cell.bm or {r=1,g=1,b=1} + cell.fo=cell.fo or {r=0,g=0,b=0} + cell.bo=cell.bo or {r=0,g=0,b=0} + render.setCell(x,y,cell) +end +function getCursorPos() + local g_cursor=df.global.cursor + if g_cursor.x ~= -30000 then + return copyall(g_cursor) + end +end +function falloff(color,sqDist,maxdist) + local v1=1/(sqDist/maxdist+1) + local v2=v1-1/(1+maxdist*maxdist) + local v=v2/(1-1/(1+maxdist*maxdist)) + return {r=v*color.r,g=v*color.g,b=v*color.b} +end +function blend(c1,c2) +return {r=math.max(c1.r,c2.r), + g=math.max(c1.g,c2.g), + b=math.max(c1.b,c2.b)} +end +LightOverlay=defclass(LightOverlay,guidm.DwarfOverlay) +LightOverlay.ATTRS { + lightMap={}, + dynamic=true, + dirty=false, +} +function LightOverlay:init(args) + + self.tick=df.global.cur_year_tick_advmode +end + +function lightPassable(shape) + if shape==df.tiletype_shape.WALL or + shape==df.tiletype_shape.BROOK_BED or + shape==df.tiletype_shape.TREE then + return false + else + return true + end +end +function circle(xm, ym,r,plot) + local x = -r + local y = 0 + local err = 2-2*r -- /* II. Quadrant */ + repeat + plot(xm-x, ym+y);--/* I. Quadrant */ + plot(xm-y, ym-x);--/* II. Quadrant */ + plot(xm+x, ym-y);--/* III. Quadrant */ + plot(xm+y, ym+x);--/* IV. Quadrant */ + r = err; + if (r <= y) then + y=y+1 + err =err+y*2+1; --/* e_xy+e_y < 0 */ + end + if (r > x or err > y) then + x=x+1 + err =err+x*2+1; --/* e_xy+e_x > 0 or no 2nd y-step */ + end + until (x >= 0); +end +function line(x0, y0, x1, y1,plot) + local dx = math.abs(x1-x0) + local dy = math.abs(y1-y0) + local sx,sy + if x0 < x1 then sx = 1 else sx = -1 end + if y0 < y1 then sy = 1 else sy = -1 end + local err = dx-dy + + while true do + if not plot(x0,y0) then + return + end + if x0 == x1 and y0 == y1 then + break + end + local e2 = 2*err + if e2 > -dy then + err = err - dy + x0 = x0 + sx + end + if x0 == x1 and y0 == y1 then + if not plot(x0,y0) then + return + end + break + end + if e2 < dx then + err = err + dx + y0 = y0 + sy + end + end +end +function LightOverlay:calculateFovs() + self.fovs=self.fovs or {} + self.precalc=self.precalc or {} + for k,v in ipairs(self.fovs) do + self:calculateFov(v.pos,v.radius,v.color) + end +end +function LightOverlay:calculateFov(pos,radius,color) + local vp=self:getViewport() + local map = self.df_layout.map + local ray=function(tx,ty) + local power=copyall(color) + local lx=pos.x + local ly=pos.y + local setTile=function(x,y) + if x>0 and y>0 and x<=map.width and y<=map.height then + local dtsq=(lx-x)*(lx-x)+(ly-y)*(ly-y) + local dt=math.sqrt(dtsq) + local tile=x+y*map.width + if self.precalc[tile] then + local tcol=blend(self.precalc[tile],power) + if tcol.r==self.precalc[tile].r and tcol.g==self.precalc[tile].g and self.precalc[tile].b==self.precalc[tile].b + and dtsq>0 then + return false + end + end + local ocol=self.lightMap[tile] or {r=0,g=0,b=0} + local ncol=blend(power,ocol) + + self.lightMap[tile]=ncol + local v=self.ocupancy[tile] + if dtsq>0 then + power.r=power.r*(v.r^dt) + power.g=power.g*(v.g^dt) + power.b=power.b*(v.b^dt) + end + lx=x + ly=y + local pwsq=power.r*power.r+power.g*power.g+power.b*power.b + return pwsq>levelDim*levelDim + end + return false + end + line(pos.x,pos.y,tx,ty,setTile) + end + circle(pos.x,pos.y,radius,ray) +end +function LightOverlay:placeLightFov(pos,radius,color) + local map = self.df_layout.map + local tile=pos.x+pos.y*map.width + local ocol=self.precalc[tile] or {r=0,g=0,b=0} + local ncol=blend(color,ocol) + self.precalc[tile]=ncol + local ocol=self.lightMap[tile] or {r=0,g=0,b=0} + local ncol=blend(color,ocol) + self.lightMap[tile]=ncol + table.insert(self.fovs,{pos=pos,radius=radius,color=color}) +end +function LightOverlay:placeLightFov2(pos,radius,color,f,rays) + f=f or falloff + local raycount=rays or 25 + local vp=self:getViewport() + local map = self.df_layout.map + local off=math.random(0,math.pi) + local done={} + for d=0,math.pi*2,math.pi*2/raycount do + local dx,dy + dx=math.cos(d+off) + dy=math.sin(d+off) + local cx=0 + local cy=0 + + for dt=0,radius,0.01 do + if math.abs(math.floor(dt*dx)-cx)>0 or math.abs(math.floor(dt*dy)-cy)> 0then + local x=cx+pos.x + local y=cy+pos.y + + if x>0 and y>0 and x<=map.width and y<=map.height and not done[tile] then + local tile=x+y*map.width + done[tile]=true + local ncol=f(color,dt*dt,radius) + local ocol=self.lightMap[tile] or {r=0,g=0,b=0} + ncol=blend(ncol,ocol) + self.lightMap[tile]=ncol + + + if --(ncol.r==ocol.r and ncol.g==ocol.g and ncol.b==ocol.b) or + not self.ocupancy[tile] then + break + end + end + cx=math.floor(dt*dx) + cy=math.floor(dt*dy) + end + end + end +end +function LightOverlay:placeLight(pos,radius,color,f) + f=f or falloff + local vp=self:getViewport() + local map = self.df_layout.map + + for i=-radius,radius do + for j=-radius,radius do + local x=pos.x+i+1 + local y=pos.y+j+1 + if x>0 and y>0 and x<=map.width and y<=map.height then + local tile=x+y*map.width + local ncol=f(color,(i*i+j*j),radius) + local ocol=self.lightMap[tile] or {r=0,g=0,b=0} + self.lightMap[tile]=blend(ncol,ocol) + end + end + end +end +function LightOverlay:calculateLightLava() + local vp=self:getViewport() + local map = self.df_layout.map + for i=map.x1,map.x2 do + for j=map.y1,map.y2 do + local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z} + local pos2={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z-1} + local t1=dfhack.maps.getTileFlags(pos) + local tt=dfhack.maps.getTileType(pos) + if tt then + local shape=tile_attrs[tt].shape + local t2=dfhack.maps.getTileFlags(pos2) + if (t1 and t1.liquid_type and t1.flow_size>0) or + (shape==df.tiletype_shape.EMPTY and t2 and t2.liquid_type and t2.flow_size>0) then + --self:placeLight({x=i,y=j},5,{r=0.8,g=0.2,b=0.2}) + self:placeLightFov({x=i,y=j},5,{r=0.8,g=0.2,b=0.2},nil) + end + end + end + end +end +function LightOverlay:calculateLightSun() + local vp=self:getViewport() + local map = self.df_layout.map + for i=map.x1,map.x2+1 do + for j=map.y1,map.y2+1 do + local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z} + + local t1=dfhack.maps.getTileFlags(pos) + + if (t1 and t1.outside ) then + + self:placeLightFov({x=i,y=j},15,{r=1,g=1,b=1},nil) + end + end + end +end +function LightOverlay:calculateLightCursor() + local c=getCursorPos() + + if c then + + local vp=self:getViewport() + local pos=vp:tileToScreen(c) + --self:placeLight(pos,11,{r=0.96,g=0.84,b=0.03}) + self:placeLightFov({x=pos.x+1,y=pos.y+1},11,{r=0.96,g=0.84,b=0.03}) + + end +end +function LightOverlay:buildOcupancy() + self.ocupancy={} + local vp=self:getViewport() + local map = self.df_layout.map + for i=map.x1,map.x2+1 do + for j=map.y1,map.y2+1 do + local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z} + local tile=i+j*map.width + local tt=dfhack.maps.getTileType(pos) + local t1=dfhack.maps.getTileFlags(pos) + if tt then + local shape=tile_attrs[tt].shape + if not lightPassable(shape) then + self.ocupancy[tile]={r=0,g=0,b=0} + else + if t1 and not t1.liquid_type and t1.flow_size>2 then + self.ocupancy[tile]={r=0.5,g=0.5,b=0.7} + else + self.ocupancy[tile]={r=0.8,g=0.8,b=0.8} + end + end + end + end + end +end +function LightOverlay:changed() + if self.dirty or self.tick~=df.global.cur_year_tick_advmode then + self.dirty=false + self.tick=df.global.cur_year_tick_advmode + return true + end + return false +end +function LightOverlay:makeLightMap() + if not self:changed() then + return + end + self.fovs={} + self.precalc={} + self.lightMap={} + + self:buildOcupancy() + self:calculateLightCursor() + self:calculateLightLava() + self:calculateLightSun() + + self:calculateFovs() +end +function LightOverlay:onIdle() + self._native.parent:logic() +end +function LightOverlay:render(dc) + if self.dynamic then + self:makeLightMap() + end + self:renderParent() + local vp=self:getViewport() + local map = self.df_layout.map + + self.lightMap=self.lightMap or {} + render.lockGrids() + render.invalidate({x=map.x1,y=map.y1,w=map.width,h=map.height}) + render.resetGrids() + for i=map.x1,map.x2 do + for j=map.y1,map.y2 do + local v=self.lightMap[i+j*map.width] + if v then + setCell(i,j,{fm=v,bm=v}) + else + local dimRgb={r=levelDim,g=levelDim,b=levelDim} + setCell(i,j,{fm=dimRgb,bm=dimRgb}) + end + end + end + render.unlockGrids() + +end +function LightOverlay:onDismiss() + render.lockGrids() + render.resetGrids() + render.invalidate() + render.unlockGrids() + +end +function LightOverlay:onInput(keys) + if keys.STRING_A096 then + self:dismiss() + else + self:sendInputToParent(keys) + + if keys.CHANGETAB then + self:updateLayout() + end + if keys.STRING_A126 and not self.dynamic then + self:makeLightMap() + end + self.dirty=true + end +end +if not render.isEnabled() then + qerror("Lua rendermode not enabled!") +end +local dyn=true +if #args>0 and args[1]=="static" then dyn=false end +local lview = LightOverlay{ dynamic=dyn} +lview:show() \ No newline at end of file diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index ea93aa262..5eb554c1c 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,4 +1,6 @@ -- allows to do jobs in adv. mode. + +--keybinding, change to your hearts content. Only the key part. keybinds={ nextJob={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, prevJob={key="CUSTOM_SHIFT_R",desc="Previous job in the list"}, @@ -10,7 +12,17 @@ up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, workshop={key="CHANGETAB",desc="Show building menu"}, } - +-- building filters +build_filter={ +forbid_all=true, --this forbits all except the "allow" +allow={"MetalSmithsForge"}, --ignored if forbit_all=false +forbid={"Custom"} --ignored if forbit_all==true +} +build_filter.HUMANish={ +forbid_all=true, +allow={"Masons"}, +forbid={} +} local gui = require 'gui' local wid=require 'gui.widgets' @@ -24,8 +36,35 @@ local tile_attrs = df.tiletype.attrs settings={build_by_items=false,check_inv=false,df_assign=true} - - +function hasValue(tbl,val) + for k,v in pairs(tbl) do + if v==val then + return true + end + end + return false +end +function reverseRaceLookup(id) + return df.global.world.raws.creatures.all[id].creature_id +end +function deon_filter(name,type_id,subtype_id,custom_id, parent) + print(name) + local adv=df.global.world.units.active[0] + local race_filter=build_filter[reverseRaceLookup(adv.race)] + if race_filter then + if race_filter.forbid_all then + return hasValue(race_filter.allow,name) + else + return not hasValue(race_filter.forbid,name) + end + else + if build_filter.forbid_all then + return hasValue(build_filter.allow,name) + else + return not hasValue(build_filter.forbid,name) + end + end +end local mode_name for k,v in ipairs({...}) do --setting parsing if v=="-c" or v=="--cheat" then @@ -176,6 +215,14 @@ function SetCreatureRef(args) end end +function SetWebRef(args) + local pos=args.pos + for k,v in pairs(df.global.world.items.other.ANY_WEBS) do + if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then + job.general_refs:insert("#",{new=df.general_ref_item,item_id=v.id}) + end + end +end function SetPatientRef(args) local job=args.job local pos=args.pos @@ -785,7 +832,7 @@ function AssignJobToBuild(args) if bld~=nil then CheckAndFinishBuilding(args,bld) else - bdialog.BuildingDialog{on_select=dfhack.curry(BuildingChosen,args),hide_none=true}:show() + bdialog.BuildingDialog{on_select=dfhack.curry(BuildingChosen,args),hide_none=true,building_filter=deon_filter}:show() end return true end @@ -854,6 +901,7 @@ actions={ {"Build" ,AssignJobToBuild,{NoConstructedBuilding}}, {"BuildLast" ,BuildLast,{NoConstructedBuilding}}, {"Clean" ,df.job_type.Clean,{}}, + {"GatherWebs" ,df.job_type.CollectWebs,{--[[HasWeb]]},{SetWebRef}}, }