Files
commandergenius/project/jni/application/enigma/src/display.cpp
2010-10-13 17:30:44 +03:00

2133 lines
57 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (C) 2002,2003,2004,2005 Daniel Heck
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
/*
* This file contains the code renders the graphics during the game
* and in the editor. This includes displaying the current landscape
* with all its objects and the inventory at the bottom of the screen.
*/
#include "display_internal.hh"
#include "video.hh"
#include "main.hh"
#include "ecl_sdl.hh"
#include "ecl.hh"
#include <algorithm>
#include <functional>
#include <cmath>
#include <iostream>
using namespace std;
using namespace ecl;
using namespace display;
using namespace enigma;
#include "d_engine.hh"
#include "d_models.hh"
class dRect {
public:
dRect (double x_, double y_, double w_, double h_) {
x = x_; y = y_; w = w_; h = h_;
}
double x, y, w, h;
};
Rect round_grid (const dRect &r, double w, double h) {
double x = r.x / w ;
double y = r.y / h;
double x2 = (r.x + r.w-1) / w;
double y2 = (r.y + r.h-1) / h;
Rect s (round_down<int> (x), round_down<int> (y),
round_down<int> (x2), round_down<int> (y2));
s.w -= s.x-1;
s.h -= s.y-1;
return s;
}
/* -------------------- Local variables -------------------- */
namespace
{
const int NTILESH = 20; // Default game screen width in tiles
const int NTILESV = 13; // Default game screen height in tiles
DisplayFlags display_flags = SHOW_ALL;
GameDisplay *gamedpy = 0;
bool ShowFPS = false;
}
//======================================================================
// STATUS BAR
//======================================================================
StatusBarImpl::StatusBarImpl (const ScreenArea &area)
: Window(area),
m_itemarea (),
m_models(),
m_changedp(false),
m_textview (*enigma::GetFont("statusbarfont")),
m_leveltime (0),
m_showtime_p (true),
m_counter (0),
m_showcounter_p(false),
m_showodometer_p(false),
m_interruptible(true),
m_text_active(false)
{
const video::VMInfo *vminfo = video::GetInfo();
m_itemarea = vminfo->sb_itemarea;
}
StatusBarImpl::~StatusBarImpl() {
ecl::delete_sequence(m_models.begin(), m_models.end());
m_models.clear();
}
void StatusBarImpl::set_time (double time) {
double oldtime=m_leveltime;
m_leveltime = time;
if (m_showtime_p && floor(m_leveltime)-floor(oldtime) >= 1)
m_changedp = true; // update clock
}
void StatusBarImpl::hide_text()
{
if (m_text_active) {
m_text_active = false;
m_changedp = true;
}
}
void StatusBarImpl::set_speed (double /*speed*/)
{
}
void StatusBarImpl::set_travelled_distance (double /*distance*/)
{
}
void StatusBarImpl::set_counter (int new_counter)
{
if (m_showcounter_p && new_counter != m_counter) {
m_changedp = true;
m_counter = new_counter;
}
}
void StatusBarImpl::show_move_counter (bool active)
{
if (active != m_showcounter_p) {
m_showcounter_p = active;
m_changedp = true;
}
}
void StatusBarImpl::show_odometer (bool active)
{
if (active != m_showodometer_p) {
m_showodometer_p = active;
m_changedp = true;
}
}
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
void StatusBarImpl::redraw (ecl::GC &gc, const ScreenArea &r) {
const video::VMInfo *vminfo = video::GetInfo();
ScreenArea a = get_area();
clip(gc, intersect(a, r));
blit(gc, a.x, a.y, enigma::GetImage ("inventory", ".jpg"));
// set_color (gc, 255, 0, 0);
// frame (gc, vminfo->sb_timearea);
// frame (gc, vminfo->sb_textarea);
// set_color (gc, 0, 255, 0);
// frame (gc, vminfo->sb_movesarea);
// frame (gc, vminfo->sb_itemarea);
if (m_showtime_p || m_showcounter_p) {
const int BUFSIZE = 8;
char buf[BUFSIZE];
int xsize_time = 0;
int xsize_moves = 0;
Surface *s_time = 0;
Surface *s_moves = 0;
Font *timefont = enigma::GetFont ("timefont");
Font *movesfont = enigma::GetFont ("smallfont");
ScreenArea timearea = vminfo->sb_timearea;
ScreenArea movesarea = vminfo->sb_movesarea;
if (m_showtime_p) {
double abstime = m_leveltime >= 0 ? m_leveltime : fabs(floor(m_leveltime));
int minutes = static_cast<int>(abstime/60);
int seconds = static_cast<int>(abstime) % 60;
if (minutes >= 100) {
minutes = 99;
seconds = 59;
}
snprintf(buf, BUFSIZE,
m_leveltime >= 0 ? "%d:%02d" : "-%d:%02d",
minutes, seconds);
s_time = timefont->render(buf);
xsize_time = s_time->width();
}
if (m_showcounter_p) {
int len = snprintf(buf, BUFSIZE, "%d", m_counter);
s_moves = movesfont->render(buf);
xsize_moves = s_moves->width();
}
if (m_showtime_p) {
if (m_showcounter_p) { // time + moves
int x = timearea.x + (movesarea.x - timearea.x - xsize_time)/2;
int y = timearea.y + (timearea.h - timefont->get_lineskip())/2;
blit(gc, x, y, s_time);
x = movesarea.x + (movesarea.w - xsize_moves)/2;
y = movesarea.y + (movesarea.h + timefont->get_lineskip())/2 - movesfont->get_lineskip() - 4;
blit(gc, x, y, s_moves);
}
else { // only time
int x = timearea.x + (timearea.w - xsize_time)/2;
int y = timearea.y + (timearea.h - timefont->get_lineskip())/2;
blit(gc, x, y, s_time);
}
}
else { // only moves
int x = timearea.x + (timearea.w - xsize_moves)/2;
int y = timearea.y + (timearea.h - movesfont->get_lineskip())/2;
blit(gc, x, y, s_moves);
}
delete s_moves;
delete s_time;
}
if (m_text_active) {
m_textview.draw (gc, r);
}
else {
int itemsize = static_cast<int>(vminfo->tile_size * 1.25);
int x = m_itemarea.x;
for (unsigned i=0; i<m_models.size(); ++i) {
Model *m = m_models[i];
m->draw(gc, x, m_itemarea.y);
x += itemsize;
}
}
m_changedp = false;
}
void StatusBarImpl::set_inventory (const std::vector<std::string> &modelnames)
{
if (m_text_active && m_interruptible) {
hide_text();
}
ecl::delete_sequence(m_models.begin(), m_models.end());
m_models.clear();
for (size_t i=0; i<modelnames.size(); ++i) {
m_models.push_back(MakeModel(modelnames[i]));
}
m_changedp = true;
}
void StatusBarImpl::show_text (const std::string &str, bool scrolling, double duration)
{
m_textview.set_text (str, scrolling, duration);
m_interruptible = false;
m_text_active = true;
m_changedp = true;
}
void StatusBarImpl::tick (double dtime) {
// Update text display
if (m_text_active) {
m_textview.tick (dtime);
m_changedp = m_changedp || m_textview.has_changed();
if (m_textview.has_finished()) {
m_text_active = false;
m_changedp = true;
}
}
}
void StatusBarImpl::new_world() {
delete_sequence(m_models.begin(), m_models.end());
m_models.clear();
m_leveltime = 0;
m_text_active = false;
m_changedp = true;
}
/* -------------------- TextDisplay implementation -------------------- */
TextDisplay::TextDisplay (Font &f)
: area(),
text(),
changedp(false), finishedp(true),
pingpong (false),
showscroll(false),
xoff(0), scrollspeed(200),
textsurface(0), font(f)
{
const video::VMInfo *vminfo = video::GetInfo();
area = vminfo->sb_textarea;
time = maxtime = 0;
}
void TextDisplay::set_text (const string &t, bool scrolling, double duration)
{
text = t;
textsurface.reset(font.render(text.c_str()));
time = 0;
if (scrolling) {
if (duration <= 0) {
xoff = -area.w;
scrollspeed = 160;
} else {
// Showscroll mode: first show string then scoll it out
showscroll = true;
scrollspeed = 0;
if (area.w < textsurface->width()) {
// start left adjusted for long strings
xoff = 0;
} else {
// start centered for short strings
xoff = -(area.w - textsurface->width())/2;
}
}
}
if (duration > 0)
maxtime = duration;
else
maxtime = 1e20; // "infinite" for all practical purposes
if (!scrolling) {// centered text string
if (area.w < textsurface->width()) {
pingpong = true;
scrollspeed = 4 * (textsurface->width() - area.w) / duration;
xoff = 0;
}
else {
pingpong = false;
xoff = -(area.w - textsurface->width())/2;
scrollspeed = 0;
}
}
finishedp = false;
changedp = true;
}
void TextDisplay::tick (double dtime)
{
time += dtime;
if (time > maxtime) {
if (showscroll) {
showscroll = false;
scrollspeed = 160;
maxtime = 1e20; // "infinite" for all practical purposes
} else {
finishedp = true;
changedp = true;
}
}
else {
int oldxoff = round_nearest<int>(xoff);
xoff += dtime * scrollspeed;
int newxoff = round_nearest<int> (xoff);
changedp = newxoff != oldxoff;
if (pingpong) {
if (scrollspeed > 0 && area.w + newxoff >= textsurface->width() ) {
scrollspeed = -scrollspeed;
}
else if (scrollspeed < 0 && newxoff <= 0) {
scrollspeed = -scrollspeed;
}
}
else if (xoff >= textsurface->width()) {
finishedp = true;
changedp = true;
}
}
}
void TextDisplay::draw (ecl::GC &gc, const ScreenArea &r) {
clip(gc, intersect(area, r));
set_color(gc, 0,0,0);
box(gc, area);
if (Surface *s = textsurface.get())
blit(gc, area.x-round_nearest<int>(xoff), area.y, s);
}
//======================================================================
// DISPLAY ENGINE
//======================================================================
DisplayEngine::DisplayEngine (int tilew, int tileh)
: m_tilew(tilew), m_tileh(tileh),
m_offset(),
m_new_offset(),
m_area (),
m_width(0), m_height(0),
m_redrawp(0,0)
{
m_area = video::GetScreen()->size();
m_screenoffset[0] = m_screenoffset[1] = 0;
}
DisplayEngine::~DisplayEngine()
{
delete_sequence (m_layers.begin(), m_layers.end());
}
void DisplayEngine::set_tilesize (int w, int h)
{
m_tilew=w;
m_tileh=h;
}
void DisplayEngine::add_layer (DisplayLayer *l) {
l->set_engine (this);
m_layers.push_back(l);
}
void DisplayEngine::set_offset (const V2 &off) {
m_offset = m_new_offset = off;
world_to_video (off, &m_screenoffset[0], &m_screenoffset[1]);
}
void DisplayEngine::move_offset (const ecl::V2 &off) {
m_new_offset = off;
}
/*! Scroll the screen contents and mark the newly exposed regions for
redraw. This method assumes that the screen contents were not
modified externally since the last call to update_offset(). */
void DisplayEngine::update_offset ()
{
ecl::Screen *screen = video::GetScreen();
int oldx = m_screenoffset[0];
int oldy = m_screenoffset[1];
int newx, newy;
world_to_video (m_new_offset, &newx, &newy);
if (newx != oldx || newy != oldy) {
const Rect &a = get_area();
Rect oldarea (a.x+oldx, a.y+oldy, a.w, a.h);
Rect newarea (a.x+newx, a.y+newy, a.w, a.h);
Rect common = intersect(newarea, oldarea);
// Blit overlapping screen area from old to new position
GC screengc (screen->get_surface());
Rect blitrect (common.x-oldx, common.y-oldy, common.w, common.h);
blit (screengc, common.x-newx, common.y-newy, screen->get_surface(), blitrect);
blitrect.x = common.x-newx;
blitrect.y = common.y-newy;
screen->update_rect(blitrect);
// Update offset
set_offset (V2(newx/double(m_tilew), newy/double(m_tileh)));
// Mark areas that could not be copied from old screen for redraw
RectList rl;
rl.push_back (get_area());
rl.sub (blitrect);
for (RectList::iterator i=rl.begin(); i!=rl.end(); ++i) {
Rect r = screen_to_world(*i);
mark_redraw_area (r);
}
}
}
void DisplayEngine::set_screen_area (const ecl::Rect & r) {
m_area = r;
}
void DisplayEngine::new_world (int w, int h)
{
m_width = w;
m_height = h;
m_offset = m_new_offset = V2();
m_screenoffset[0] = m_screenoffset[1] = 0;
m_redrawp.resize(w, h, 1);
for (unsigned i=0; i<m_layers.size(); ++i)
m_layers[i]->new_world(w,h);
}
void DisplayEngine::tick (double dtime) {
for_each (m_layers.begin(), m_layers.end(),
bind2nd (mem_fun(&DisplayLayer::tick), dtime));
}
void DisplayEngine::world_to_screen (const V2 & pos, int *x, int *y)
{
*x = round_nearest<int>(pos[0]*m_tilew) - m_screenoffset[0] + get_area().x;
*y = round_nearest<int>(pos[1]*m_tileh) - m_screenoffset[1] + get_area().y;
}
void DisplayEngine::world_to_video (const ecl::V2 &pos, int *x, int *y)
{
*x = round_nearest<int>(pos[0]*m_tilew);
*y = round_nearest<int>(pos[1]*m_tileh);
}
void DisplayEngine::video_to_screen (int x, int y, int *xx, int *yy)
{
*xx = x - m_screenoffset[0] + get_area().x;
*yy = y - m_screenoffset[1] + get_area().y;
}
/* Calculate the smallest rectangle `s' in world space aligned to
tiles that contains a certain rectangle `r' in video space. This
function is used for calculating the region that needs to be
updated when a sprite with extension `r' is moved on the screen. */
void DisplayEngine::video_to_world (const ecl::Rect &r, Rect &s) {
dRect dr (r.x, r.y, r.w, r.h);
s = round_grid (dr, get_tilew(), get_tileh());
}
ScreenArea DisplayEngine::world_to_screen (const WorldArea &a)
{
int x, y;
world_to_screen (V2(a.x, a.y), &x, &y);
return ScreenArea (x, y, a.w*m_tilew, a.h*m_tileh);
}
WorldArea DisplayEngine::screen_to_world (const ScreenArea &a)
{
int sx = m_screenoffset[0] + a.x - get_area().x;
int sy = m_screenoffset[1] + a.y - get_area().y;
int x1 = Max(0, sx / m_tilew);
int y1 = Max(0, sy / m_tileh);
int x2 = Min(m_width, (sx + a.w+m_tilew-1) / m_tilew);
int y2 = Min(m_height, (sy + +a.h+m_tileh-1) / m_tileh);
return WorldArea (x1, y1, x2-x1, y2-y1);
}
V2 DisplayEngine::to_world (const V2 &pos) {
return m_offset +
V2((pos[0]-get_area().x)/m_tilew, (pos[1]-get_area().y)/m_tileh);
}
void DisplayEngine::mark_redraw_area (const WorldArea &wa, int delay)
{
int x2 = Min(m_width, wa.x+wa.w);
int y2 = Min(m_height, wa.y+wa.h);
for (int x=Max(0,wa.x); x <x2; x++)
for (int y=Max(0,wa.y); y<y2; y++) {
int d = m_redrawp(x, y);
if (d == 0 || 1+delay < d)
m_redrawp(x,y) = 1 + delay;
}
}
void DisplayEngine::mark_redraw_screen() {
mark_redraw_area(screen_to_world(m_area));
}
void DisplayEngine::draw_all (ecl::GC &gc)
{
WorldArea wa = screen_to_world (get_area());
// Fill screen area not covered by world
{
RectList rl;
rl.push_back (get_area());
rl.sub (world_to_screen (WorldArea (0,0, m_width, m_height)));
set_color (gc, 200, 0, 200);
for (RectList::iterator i=rl.begin(); i!=rl.end(); ++i)
box (gc, *i);
}
int xpos, ypos;
world_to_screen (V2(wa.x, wa.y), &xpos, &ypos);
for (unsigned i=0; i<m_layers.size(); ++i) {
clip(gc, get_area());
m_layers[i]->prepare_draw (wa);
m_layers[i]->draw (gc, wa, xpos, ypos);
m_layers[i]->draw_onepass(gc);
}
}
void DisplayEngine::update_layer (DisplayLayer *l, WorldArea wa)
{
GC gc(video::GetScreen()->get_surface());
int x2 = wa.x+wa.w;
int y2 = wa.y+wa.h;
clip(gc, get_area());
int xpos0, ypos;
world_to_screen (V2(wa.x, wa.y), &xpos0, &ypos);
l->prepare_draw (wa);
for (int y=wa.y; y<y2; y++, ypos += m_tileh) {
int xpos = xpos0;
for (int x=wa.x; x<x2; x++, xpos += m_tilew) {
if (m_redrawp(x,y) == 1)
l->draw (gc, WorldArea(x,y,1,1), xpos, ypos);
}
}
l->draw_onepass (gc);
}
void DisplayEngine::update_screen()
{
ecl::Screen *screen = video::GetScreen();
GC gc(screen->get_surface());
if (m_new_offset != m_offset) {
update_offset ();
m_new_offset = m_offset;
}
Rect area=get_area();
clip(gc, area);
WorldArea wa = screen_to_world (area);
for (unsigned i=0; i<m_layers.size(); ++i) {
update_layer (m_layers[i], wa);
}
int x2 = wa.x+wa.w;
int y2 = wa.y+wa.h;
for (int x=wa.x; x<x2; x++)
for (int y=wa.y; y<y2; y++)
if (m_redrawp(x,y) >= 1) {
if ((m_redrawp(x,y) -= 1) == 0)
screen->update_rect (world_to_screen (WorldArea (x, y, 1, 1)));
}
}
/* -------------------- ModelLayer -------------------- */
void ModelLayer::maybe_redraw_model(Model *m, bool immediately)
{
Rect videoarea;
if (m->has_changed(videoarea)) {
int delay = immediately ? 0 : enigma::IntegerRand (0, 2);
WorldArea wa;
get_engine()->video_to_world (videoarea, wa);
get_engine()->mark_redraw_area(wa, delay);
}
}
void ModelLayer::activate (Model *m)
{
list<Model*> &am = m_active_models_new;
am.push_back(m);
}
void ModelLayer::deactivate (Model *m)
{
list<Model*> &am = m_active_models;
list<Model*>::iterator i = find(am.begin(), am.end(), m);
if (i == am.end()) {
m_active_models_new.remove(m);
} else {
*i = 0;
}
}
void ModelLayer::new_world (int, int) {
m_active_models.clear();
m_active_models_new.clear();
}
void ModelLayer::tick (double dtime)
{
ModelList &am = m_active_models;
am.remove(static_cast<Model*> (0));
am.remove_if(mem_fun(&Model::is_garbage));
// Append new active models to list
am.splice (am.end(), m_active_models_new);
/* for_each does not work; animation may remove itself during a
tick. This may happen for example when a model callback
decides to replace the old model by another one. */
for (ModelList::iterator i=am.begin(); i!=am.end(); ++i) {
if (Model *m = *i) {
m->tick(dtime);
// We have to check (*i) again because the list of active
// models can change during a tick!
if ((m = *i))
maybe_redraw_model (m);
}
}
}
/* -------------------- GridLayer -------------------- */
DL_Grid::DL_Grid(int redrawsize)
: m_models (0, 0), m_redrawsize (redrawsize)
{
}
DL_Grid::~DL_Grid() {
delete_sequence (m_models.begin(), m_models.end());
}
void DL_Grid::new_world (int w, int h) {
ModelLayer::new_world (w, h);
delete_sequence (m_models.begin(), m_models.end());
m_models.resize (w, h, 0);
}
void DL_Grid::mark_redraw (int x, int y) {
get_engine()->mark_redraw_area (WorldArea (x, y, m_redrawsize, m_redrawsize));
}
void DL_Grid::set_model (int x, int y, Model *m) {
if (!(x >= 0 && y >= 0 &&
(unsigned)x<m_models.width() &&
(unsigned)y<m_models.height()))
{
delete m; // model is owned by DL_Grid!
return;
}
if (m_models(x,y) != m) {
if (Model *oldm = m_models(x,y)) {
oldm->remove(this);
delete oldm;
}
mark_redraw (x, y);
m_models(x,y) = m;
if (m) {
int vx, vy;
get_engine()->world_to_video (V2 (x, y), &vx, &vy);
m->expose (this, vx, vy);
}
}
}
Model * DL_Grid::get_model (int x, int y) {
return m_models(x,y);
}
Model *DL_Grid::yield_model (int x, int y) {
Model *m = get_model (x, y);
if (m)
m->remove (this);
m_models(x,y) = 0;
mark_redraw (x,y);
return m;
}
void DL_Grid::draw (ecl::GC &gc, const WorldArea &a, int destx, int desty) {
int x2 = a.x+a.w;
int y2 = a.y+a.h;
int tilew = get_engine()->get_tilew();
int tileh = get_engine()->get_tileh();
int xpos = destx;
for (int x=a.x; x<x2; ++x) {
int ypos = desty;
for (int y=a.y; y<y2; ++y) {
if (Model *m = m_models(x,y))
m->draw(gc, xpos, ypos);
ypos += tileh;
}
xpos += tilew;
}
}
/* -------------------- Sprites -------------------- */
SpriteHandle::SpriteHandle (DL_Sprites *l, unsigned spriteid)
: layer(l), id(spriteid)
{}
SpriteHandle::SpriteHandle()
: layer(0)
{
id = DL_Sprites::MAGIC_SPRITEID;
}
void SpriteHandle::kill() {
if (layer) {
layer->kill_sprite (id);
layer = 0;
id = DL_Sprites::MAGIC_SPRITEID;
}
}
void SpriteHandle::move (const ecl::V2 &newpos) const {
if (layer)
layer->move_sprite (id, newpos);
}
void SpriteHandle::replace_model (Model *m) const {
if (layer)
layer->replace_sprite (id, m);
else
delete m;
}
Model *SpriteHandle::get_model () const {
return layer ? layer->get_model (id) : 0;
}
void SpriteHandle::set_callback (ModelCallback *cb) const {
if (Model *m = get_model())
m->set_callback(cb);
}
void SpriteHandle::hide() const {
if (layer) {
Sprite * s = layer->get_sprite(id);
if(s->visible) {
s->visible = false;
layer->redraw_sprite_region(id);
}
}
}
void SpriteHandle::show() const {
if (layer) {
Sprite * s = layer->get_sprite(id);
if(!s->visible) {
s->visible = true;
layer->redraw_sprite_region(id);
}
}
}
/* -------------------- Sprite layer -------------------- */
DL_Sprites::DL_Sprites()
: numsprites(0), maxsprites(1000)
{}
DL_Sprites::~DL_Sprites() {
delete_sequence(sprites.begin(), sprites.end());
}
Sprite *DL_Sprites::get_sprite(SpriteId id) {
return (id == MAGIC_SPRITEID) ? 0 : sprites[id];
}
void DL_Sprites::new_world (int w, int h) {
ModelLayer::new_world (w,h);
delete_sequence (sprites.begin(), sprites.end());
sprites.clear();
numsprites = 0;
}
void DL_Sprites::move_sprite (SpriteId id, const ecl::V2& newpos)
{
Sprite *sprite = sprites[id];
int newx, newy;
get_engine()->world_to_video (newpos, &newx, &newy);
if (newx != sprite->screenpos[0] || newy != sprite->screenpos[1] ||
sprite->mayNeedRedraw ) {
redraw_sprite_region(id); // make sure old sprite is removed
sprite->pos = newpos;
sprite->screenpos[0] = newx;
sprite->screenpos[1] = newy;
if (Anim2d* anim = dynamic_cast<Anim2d*>(sprite->model))
anim->move (newx, newy);
redraw_sprite_region(id); // draw new sprite
}
}
SpriteId DL_Sprites::add_sprite (Sprite *sprite)
{
if (numsprites >= maxsprites) {
delete sprite;
return MAGIC_SPRITEID;
}
SpriteList &sl = sprites;
SpriteId id = 0;
// Find the first empty slot
SpriteList::iterator i = find(sl.begin(), sl.end(), static_cast<Sprite*>(0));
if (i == sl.end()) {
id = sl.size();
sl.push_back(sprite);
}
else {
id = distance(sl.begin(), i);
*i = sprite;
}
get_engine()->world_to_video (sprite->pos, &sprite->screenpos[0], &sprite->screenpos[1]);
if (Model *m = sprite->model)
m->expose (this, sprite->screenpos[0], sprite->screenpos[1]);
redraw_sprite_region(id);
numsprites += 1;
return id;
}
void DL_Sprites::replace_sprite (SpriteId id, Model *m) {
Sprite *sprite = sprites[id];
if (Model *old = sprite->model) {
redraw_sprite_region(id);
old->remove (this);
delete old;
}
sprite->model = m;
if (m) {
m->expose (this, sprite->screenpos[0], sprite->screenpos[1]);
redraw_sprite_region(id);
}
}
void DL_Sprites::kill_sprite (SpriteId id) {
if (Sprite *sprite = sprites[id]) {
redraw_sprite_region(id);
if (Model *m = sprite->model) {
m->remove (this);
}
sprites[id] = 0;
numsprites -= 1;
delete sprite;
}
}
void DL_Sprites::draw (ecl::GC &gc, const WorldArea &a, int /*x*/, int /*y*/)
{
DisplayEngine *engine = get_engine();
clip (gc, intersect (engine->get_area(), engine->world_to_screen(a)));
draw_sprites (false, gc);
}
void DL_Sprites::draw_sprites (bool drawshadowp, GC &gc) {
SpriteList &sl = sprites;
for (unsigned i=0; i<sl.size(); ++i) {
Sprite *s = sl[i];
if (s && s->model && s->visible) {
int sx, sy;
get_engine()->world_to_screen(s->pos, &sx, &sy);
if (drawshadowp)
s->model->draw_shadow(gc, sx, sy);
else
s->model->draw(gc, sx, sy);
}
}
}
void DL_Sprites::draw_onepass (ecl::GC &gc)
{
// draw_sprites (false, gc);
}
void DL_Sprites::redraw_sprite_region (SpriteId id)
{
Sprite *s = sprites[id];
if (s && s->model) {
Rect r, redrawr;
s->model->get_extension (r);
r.x += s->screenpos[0];
r.y += s->screenpos[1];
DisplayEngine *e = get_engine();
e->video_to_world (r, redrawr);
e->mark_redraw_area (redrawr);
}
}
void DL_Sprites::tick (double dtime)
{
SpriteList &sl = sprites;
for (unsigned i=0; i<sl.size(); ++i) {
Sprite *s = sl[i];
if (!s || !s->model)
continue;
if (s->model->is_garbage() && s->layer==SPRITE_EFFECT) {
// Only remove effect sprites -- actor sprites remain in
// the world all the time
kill_sprite (i);
}
}
ModelLayer::tick (dtime);
}
//----------------------------------------------------------------------
// RUBBER BANDS
//----------------------------------------------------------------------
void DL_Lines::draw_onepass (ecl::GC &gc)
{
DisplayEngine *engine = get_engine();
set_color (gc, 240, 140, 20, 255);
set_flags (gc.flags, GS_ANTIALIAS);
for (LineMap::iterator i=m_rubbers.begin(); i!= m_rubbers.end(); ++i)
{
int x1, y1, x2, y2;
engine->world_to_screen (i->second.start, &x1, &y1);
engine->world_to_screen (i->second.end, &x2, &y2);
line (gc, x1, y1, x2, y2);
}
}
/* Mark the screen region occupied by a rubber band for redraw.
Problem is: what region is that exactly? What pixels on the screen
will the line rasterizer touch? Hard to tell, especially when
anti-aliasing is used.
This function constructs a list of rectangles that completely
enclose the line by subdividing the line into n segments and
constructing the bounding box for each of these segments. To
account for the (effective) finite width of the line, these boxes
need to be enlarged by a small amount to make them overlap a bit.
The number n of subdivision depends on the length of the line. n=1
would of course do, but we want to redraw as little of the screen
as possible. `n' is therefore chosen in such a way that the line
is covered with boxes of size not larger than `maxboxsize'.
*/
void DL_Lines::mark_redraw_line (const Line &r) {
const double maxboxsize = 0.5;
double w0 = r.start[0]-r.end[0];
double h0 = r.start[1]-r.end[1];
int n = int (max(abs(w0),abs(h0)) / maxboxsize)+1;
double w = w0/n;
double h = h0/n;
double overlap = 0.1;
double x = r.end[0];
double y = r.end[1];
double xoverlap = w<0 ? -overlap : overlap;
double yoverlap = h<0 ? -overlap : overlap;
for (int i=0; i<n; ++i) {
dRect dr (x-xoverlap, y-yoverlap, w+2*xoverlap, h+2*yoverlap);
WorldArea wa = round_grid (dr, 1, 1);
if (wa.w < 0) {
wa.x += wa.w;
wa.w = -wa.w;
}
if (wa.h < 0) {
wa.y += wa.h;
wa.h = -wa.h;
}
wa.w++;
wa.h++;
get_engine()->mark_redraw_area (wa);
x += w;
y += h;
}
}
RubberHandle
DL_Lines::add_line (const V2 &p1, const V2 &p2)
{
m_rubbers[m_id] = Line(p1, p2);
mark_redraw_line (m_rubbers[m_id]);
return RubberHandle(this, m_id++);
}
void DL_Lines::set_startpoint (unsigned id, const V2 &p1)
{
mark_redraw_line (m_rubbers[id]);
m_rubbers[id].start = p1;
mark_redraw_line (m_rubbers[id]);
}
void DL_Lines::set_endpoint (unsigned id, const V2 &p2)
{
mark_redraw_line (m_rubbers[id]);
m_rubbers[id].end = p2;
mark_redraw_line (m_rubbers[id]);
}
void DL_Lines::kill_line (unsigned id) {
mark_redraw_line (m_rubbers[id]);
LineMap::iterator i=m_rubbers.find(id);
if (i != m_rubbers.end())
m_rubbers.erase(i);
}
RubberHandle::RubberHandle(DL_Lines *ll, unsigned id_)
: line_layer (ll), id(id_)
{
}
void RubberHandle::update_first(const V2 &p1)
{
line_layer->set_startpoint (id, p1);
}
void RubberHandle::update_second(const V2 &p2)
{
line_layer->set_endpoint (id, p2);
}
void RubberHandle::kill()
{
line_layer->kill_line(id);
}
//----------------------------------------------------------------------
// SHADOWS
//----------------------------------------------------------------------
/*
** Drawing the shadows is a lot more difficult than drawing any of the
** other layers. There are a couple of reasons for this:
**
** 1. Both Stones and actors cast a shadow. Not a real problem, but
** it makes the implementation more complex.
**
** 2. Shadows can overlap. Not only can the shadows of stones and
** actors overlap, but also the shadows of two adjacent stones can.
** Since we are using alpha blending for the shadows, this means
** that we cannot blit the invidual shadows to the screen, but we
** have to use an intermediate buffer.
**
** 3. Performance is critical. Drawing the shadows is time-consuming,
** firstly because alpha blending is costly and secondly because of
** the intermediate buffer. So we should try to cache shadows *and*
** avoid the buffer if possible.
**
** So, how do we approach these problems? We handle stone and actor
** shadows separately: The stone shadows do not change very often so
** it's easy to cache them, one tile at a time. If there is no actor
** on this tile, we can blit the cached image directly to the screen,
** otherwise we have no choice but to use the buffer.
**
** The remaining problem is the shadow cache. The easiest solution
** would be to use one huge image for the whole level and keep it in
** memory all the time. This would consume roughly 20mb for a 100x100
** landscape, which is of course excessive, considering that there are
** rarely more than 40 different shadow tiles in each landscape.
**
** Instead, Enigma caches the most recently calculated shadow tiles in
** a linked list. (If this should one day turn out to be too slow,
** it's still possible to resort to a hash table or something
** similar.)
*/
namespace display
{
struct ImageQuad {
Image *images[4];
ImageQuad() { /* do not initialize fields. */ }
ImageQuad (Image *i1, Image *i2, Image *i3, Image *i4) {
images[0] = i1;
images[1] = i2;
images[2] = i3;
images[3] = i4;
}
bool operator == (const ImageQuad &q) {
return (images[0]==q.images[0] &&
images[1]==q.images[1] &&
images[2]==q.images[2] &&
images[3]==q.images[3]);
}
Image *operator[] (int idx) { return images[idx]; }
};
bool only_static_shadows (Model *models[4], ImageQuad &q) {
int nimages=4;
for (int i=0; i<4; ++i) {
if (models[i] == 0) {
// No model at all? -> static
q.images[i] = 0;
}
else if (Model *shadow = models[i]->get_shadow()) {
if (ImageModel *im = dynamic_cast<ImageModel*>(shadow))
// We have a model with a static image shadow
q.images[i] = im->get_image();
else
q.images[i] = 0, nimages--;
}
else
q.images[i] = 0;
}
return nimages==4;
}
struct StoneShadow {
ImageQuad images;
Surface *image;
bool in_cache;
StoneShadow (ImageQuad iq, bool cached)
: images(iq), image(0), in_cache(cached)
{}
};
class StoneShadowCache : public ecl::Nocopy {
public:
StoneShadowCache(int tilew, int tileh);
~StoneShadowCache();
StoneShadow *retrieve (Model *models[4]);
void release (StoneShadow *s);
void clear();
private:
typedef std::list<StoneShadow*> CacheList;
// Variables
size_t m_max_size; // Max. number of different shadow tiles to cache
CacheList m_cache;
int m_tilew, m_tileh;
vector<Surface *> m_surface_avail;
// Private methods.
Surface *new_surface ();
StoneShadow *find_in_cache (const ImageQuad &images);
void fill_image (StoneShadow *s);
void fill_image (StoneShadow *sh, Model *models[4]);
};
}
StoneShadowCache::StoneShadowCache(int tilew, int tileh)
: m_max_size(50), m_cache()
{
m_tilew=tilew; m_tileh=tileh;
}
StoneShadowCache::~StoneShadowCache()
{
clear();
}
void StoneShadowCache::clear() {
for (CacheList::iterator i = m_cache.begin(); i!=m_cache.end(); ++i)
delete (*i)->image;
delete_sequence (m_cache.begin(), m_cache.end());
m_cache.clear();
delete_sequence (m_surface_avail.begin(), m_surface_avail.end());
m_surface_avail.clear();
}
void StoneShadowCache::fill_image (StoneShadow *sh) {
// Special case: no shadows at all:
if (sh->images[0] == 0 && sh->images[1] == 0 &&
sh->images[2] == 0 && sh->images[3] == 0)
{
sh->image = 0;
return;
}
Surface *s = new_surface();
GC gc(s);
set_color (gc, 255,255,255);
box(gc, s->size());
if (Image *i = sh->images[0])
draw_image (i, gc, -m_tilew, -m_tileh);
if (Image *i = sh->images[1])
draw_image (i, gc, 0, -m_tileh);
if (Image *i = sh->images[2])
draw_image (i, gc, -m_tilew, 0);
if (Image *i = sh->images[3])
draw_image (i, gc, 0, 0);
SDL_Surface *ss = s->get_surface();
SDL_SetColorKey(ss, SDL_SRCCOLORKEY | SDL_RLEACCEL,
SDL_MapRGB(ss->format, 255,255,255));
SDL_SetAlpha (ss, SDL_SRCALPHA | SDL_RLEACCEL, 128);
sh->image = s;
}
void StoneShadowCache::fill_image (StoneShadow *sh, Model *models[4]) {
Surface *s = new_surface();
GC gc(s);
set_color (gc, 255,255,255);
box(gc, s->size());
if (models[0]) models[0]->draw_shadow (gc, -m_tilew, -m_tileh);
if (models[1]) models[1]->draw_shadow (gc, 0, -m_tileh);
if (models[2]) models[2]->draw_shadow (gc, -m_tilew, 0);
if (models[3]) models[3]->draw_shadow (gc, 0,0);
SDL_Surface *ss = s->get_surface();
SDL_SetColorKey(ss, SDL_SRCCOLORKEY | SDL_RLEACCEL,
SDL_MapRGB(ss->format, 255,255,255));
SDL_SetAlpha (ss, SDL_SRCALPHA | SDL_RLEACCEL, 128);
sh->image = s;
}
StoneShadow *
StoneShadowCache::find_in_cache (const ImageQuad &images)
{
CacheList::iterator i=m_cache.begin();
for (; i!=m_cache.end(); ++i) {
if ((*i)->images == images) {
StoneShadow *sh = *i;
// Move entry to front of list
m_cache.splice (m_cache.begin(), m_cache, i);
return sh;
}
}
return 0;
}
/* Try to lookup the shadow created by the four models in `models[]'
in the shadow cache. */
StoneShadow *
StoneShadowCache::retrieve (Model *models[4])
{
StoneShadow *shadow = 0;
ImageQuad images;
// Only cache static stone shadows, i.e., those consisting
// only of Image models.
if (only_static_shadows (models, images)) {
shadow = find_in_cache(images);
if (!shadow) {
shadow = new StoneShadow (images, true);
fill_image (shadow);
m_cache.push_front (shadow);
}
}
else {
shadow = new StoneShadow (images, false);
fill_image (shadow, models);
}
return shadow;
}
void StoneShadowCache::release (StoneShadow *s) {
if (s->in_cache) {
// Image is in cache, no need to free anything
}
else {
m_surface_avail.push_back(s->image);
delete s;
}
}
Surface *StoneShadowCache::new_surface () {
Surface *s = 0;
if (m_surface_avail.empty()) {
// WARNING: Always make sure the surface format here matches
// the format of `buffer' in class DL_Shadows!!!
SDL_Surface *ss = SDL_CreateRGBSurface(SDL_SWSURFACE,
m_tilew, m_tileh, 32,
0,0,0,0);
s = Surface::make_surface(ss);
} else {
s = m_surface_avail.back();
m_surface_avail.pop_back();
}
return s;
}
/* -------------------- Shadow layer -------------------- */
DL_Shadows::DL_Shadows (DL_Grid *grid, DL_Sprites *sprites)
: m_grid(grid), m_sprites(sprites), m_cache(0), buffer(0),
m_hasactor (0,0)
{
}
DL_Shadows::~DL_Shadows() {
delete m_cache;
delete buffer;
}
void DL_Shadows::new_world(int w, int h)
{
m_hasactor.resize (w, h, false);
DisplayEngine *e = get_engine();
int tilew=e->get_tilew();
int tileh=e->get_tileh();
delete m_cache;
m_cache = new StoneShadowCache(tilew, tileh);
delete buffer;
// WARNING: Always make sure the surface format here matches
// the format in `StoneShadowCache::new_surface' !!!
SDL_Surface *ss = SDL_CreateRGBSurface(SDL_SWSURFACE,
tilew, tileh, 32,
0,0,0,0);
SDL_SetAlpha(ss, SDL_SRCALPHA, 128);
SDL_SetColorKey(ss, SDL_SRCCOLORKEY ,
SDL_MapRGB(ss->format, 255,255,255));
buffer = Surface::make_surface(ss);
}
void DL_Shadows::draw (ecl::GC &gc, const WorldArea &a, int destx, int desty) {
int x2 = a.x+a.w;
int y2 = a.y+a.h;
int tilew = get_engine()->get_tilew();
int tileh = get_engine()->get_tileh();
int xpos = destx;
for (int x=a.x; x<x2; ++x) {
int ypos = desty;
for (int y=a.y; y<y2; ++y) {
draw (gc, xpos, ypos, x, y);
ypos += tileh;
}
xpos += tilew;
}
}
bool DL_Shadows::has_actor (int x, int y)
{
return m_hasactor(x, y);
}
/** Prepare the shadows layer for a redraw. This routine
pre-calculates the tiles that currently are partially covered by
an actor. */
void DL_Shadows::prepare_draw (const WorldArea &wa)
{
for (int i=0; i<wa.w; ++i)
for (int j=0; j<wa.h; ++j)
m_hasactor(wa.x + i, wa.y + j) = false;
for (unsigned k=0; k<m_sprites->sprites.size(); ++k) {
Sprite *s = m_sprites->sprites[k];
if (s && s->layer == SPRITE_ACTOR && s->model) {
Rect r, redrawr;
s->model->get_extension (r);
r.x += s->screenpos[0];
r.y += s->screenpos[1];
DisplayEngine *e = get_engine();
e->video_to_world (r, redrawr);
redrawr.intersect (wa);
for (int i=0; i<redrawr.w; ++i)
for (int j=0; j<redrawr.h; ++j)
m_hasactor(redrawr.x + i, redrawr.y + j) = true;
}
}
}
Model *DL_Shadows::get_shadow_model(int x, int y) {
if (x >= 0 && y >= 0) {
if (Model *m = m_grid->get_model(x,y))
return m; //return m->get_shadow();
}
return 0;
}
void DL_Shadows::draw(GC &gc, int xpos, int ypos, int x, int y) {
Model *models[4];
models[0] = get_shadow_model (x-1, y-1);
models[1] = get_shadow_model (x, y-1);
models[2] = get_shadow_model (x-1, y);
models[3] = get_shadow_model (x, y);
StoneShadow *sh = m_cache->retrieve (models);
int tilew = get_engine()->get_tilew();
int tileh = get_engine()->get_tileh();
bool hasActor = this->has_actor (x, y);
if (hasActor || sh->image) {
Surface *s = sh->image;
if (hasActor) {
GC gc2(buffer);
if (s) {
s->lock();
buffer->lock();
SDL_Surface *ss = s->get_surface();
SDL_Surface *bs = buffer->get_surface();
memcpy (bs->pixels, ss->pixels, ss->w * ss->h * ss->format->BytesPerPixel);
buffer->unlock();
s->unlock();
}
else {
set_color (gc2, 255, 255, 255);
box (gc2, buffer->size());
}
for (unsigned i=0; i<m_sprites->sprites.size(); ++i) {
if (Sprite *sp = m_sprites->sprites[i]) {
if (sp->visible && sp->model) {
int sx = round_nearest<int>(sp->pos[0]*tilew) - x*tilew;
int sy = round_nearest<int>(sp->pos[1]*tileh) - y*tileh;
sp->model->draw_shadow(gc2, sx, sy);
}
}
}
blit(gc, xpos, ypos, buffer);
}
else {
blit (gc, xpos,ypos,s);
}
}
m_cache->release (sh);
}
//----------------------------------------------------------------------
// Sprite following code
//----------------------------------------------------------------------
Follower::Follower (DisplayEngine *e)
: m_boundary (0.5),
m_engine(e)
{}
double Follower::get_hoff() const
{
ScreenArea gamearea = m_engine->get_area();
return gamearea.w / m_engine->get_tilew() -m_boundary*2;
}
double Follower::get_voff() const
{
ScreenArea gamearea = m_engine->get_area();
return gamearea.h / m_engine->get_tileh() -m_boundary*2;
}
void Follower::center(const ecl::V2 &point)
{
double borderh = m_boundary;
double borderv = m_boundary;
double hoff = get_hoff();
double voff = get_voff();
V2 off = point;
off[0] = floor((off[0] - borderh) / hoff) * hoff;
off[1] = floor((off[1] - borderv) / voff) * voff;
set_offset(off);
}
bool Follower::set_offset (V2 offs)
{
DisplayEngine *e = get_engine();
offs[0] = max (offs[0], 0.0);
offs[1] = max (offs[1], 0.0);
offs[0] = min (offs[0], double(e->get_width()-get_hoff()-1));
offs[1] = min (offs[1], double(e->get_height()-get_voff()-1));
if (offs != e->get_offset()) {
e->set_offset(offs);
return true;
}
return false;
}
/* -------------------- Follower_Screen -------------------- */
Follower_Screen::Follower_Screen(DisplayEngine *e)
: Follower(e)
{}
/*! Determine whether the screen must be scrolled or not, and change
the coordinate origin of the screen accordingly. */
void Follower_Screen::tick(double, const ecl::V2 &point) {
DisplayEngine *engine = get_engine();
V2 oldoff = engine->get_offset();
Follower::center(point);
if (oldoff != engine->get_offset())
engine->mark_redraw_screen();
}
/* -------------------- Follower_Scrolling -------------------- */
Follower_Scrolling::Follower_Scrolling(DisplayEngine *e, bool screenwise_)
: Follower (e),
currently_scrolling(false),
scrollspeed(0), resttime(0),
screenwise (screenwise_)
{}
void Follower_Scrolling::center(const ecl::V2 &point)
{
Follower::center(point);
curpos = destpos = get_engine()->get_offset();
}
void Follower_Scrolling::tick(double dtime, const ecl::V2 &point)
{
DisplayEngine *engine = get_engine();
if (!currently_scrolling) {
ScreenArea gamearea = engine->get_area();
int tilew = engine->get_tilew();
int tileh = engine->get_tileh();
int borderx = tilew/2;
int bordery = tileh/2;
int sx, sy;
engine->world_to_screen(point, &sx, &sy);
bool scrollx_p = (sx < gamearea.x + borderx)
|| (sx >= gamearea.x + gamearea.w - borderx);
bool scrolly_p = (sy < gamearea.y + bordery)
|| (sy >= gamearea.y + gamearea.h - bordery);
if (scrollx_p || scrolly_p) {
V2 olddest = destpos;
V2 scrollpos = engine->get_offset();
currently_scrolling = true;
// Move `point' to center of the screen
curpos = scrollpos;
if (screenwise) {
double hoff = get_hoff();
double voff = get_voff();
destpos[0] = floor((point[0]-m_boundary) / hoff) * hoff;
destpos[1] = floor((point[1]-m_boundary) / voff) * voff;
} else {
destpos = point - V2(gamearea.w/tilew, gamearea.h/tileh)/2;
}
// Round to integer pixel offset
destpos[0] = round_nearest<int>(destpos[0]*tilew)/tilew;
destpos[1] = round_nearest<int>(destpos[1]*tileh)/tileh;
// Don't scroll off the game area
destpos[0] = Clamp (destpos[0], 0.0,
(double)engine->get_width()-gamearea.w/tilew);
destpos[1] = Clamp (destpos[1], 0.0,
(double)engine->get_height()-gamearea.h/tileh);
if (!scrollx_p)
destpos[0] = olddest[0];
if (!scrolly_p)
destpos[1] = olddest[1];
}
}
if (currently_scrolling) {
scrollspeed = 45.0;
resttime = length(destpos - curpos)/scrollspeed;
resttime -= dtime;
if (resttime <= 0) {
engine->move_offset (destpos);
currently_scrolling = false;
} else {
dir = normalize(destpos - curpos);
curpos += dir * scrollspeed*dtime;
engine->move_offset (curpos);
}
}
}
/* -------------------- Follower_Smooth -------------------- */
Follower_Smooth::Follower_Smooth (DisplayEngine *e)
: Follower (e)
{
}
ecl::V2 Follower_Smooth::calc_offset (const ecl::V2 &point)
{
DisplayEngine *engine = get_engine();
ScreenArea gamearea = engine->get_area();
int tilew = engine->get_tilew();
int tileh = engine->get_tileh();
V2 destpos = point - V2(double(gamearea.w)/tilew, double(gamearea.h)/tileh)/2;
// Round to integer pixel offset
destpos[0] = round_nearest<int>(destpos[0]*tilew)/double(tilew);
destpos[1] = round_nearest<int>(destpos[1]*tileh)/double(tileh);
destpos[0] = Clamp (destpos[0], 0.0,
(double)engine->get_width()-gamearea.w/tilew);
destpos[1] = Clamp (destpos[1], 0.0,
(double)engine->get_height()-gamearea.h/tileh);
return destpos;
}
void Follower_Smooth::tick (double /*time*/, const ecl::V2 &point)
{
DisplayEngine *engine = get_engine();
engine->move_offset (calc_offset (point));
}
void Follower_Smooth::center (const ecl::V2 &point)
{
set_offset(calc_offset (point));
}
//----------------------------------------------------------------------
// Editor / game display engine
//----------------------------------------------------------------------
CommonDisplay::CommonDisplay (const ScreenArea &a)
{
m_engine = new DisplayEngine;
m_engine->set_screen_area (a);
const video::VMInfo *vminfo = video::GetInfo();
m_engine->set_tilesize (vminfo->tile_size, vminfo->tile_size);
// Create and configure display layers
floor_layer = new DL_Grid;
item_layer = new DL_Grid;
sprite_layer = new DL_Sprites;
stone_layer = new DL_Grid (2);
shadow_layer = new DL_Shadows(stone_layer, sprite_layer);
line_layer = new DL_Lines;
effects_layer = new DL_Sprites;
effects_layer->set_maxsprites(50);
// Register display layers
m_engine->add_layer (floor_layer);
m_engine->add_layer (item_layer);
m_engine->add_layer (shadow_layer);
m_engine->add_layer (sprite_layer);
m_engine->add_layer (stone_layer);
m_engine->add_layer (line_layer);
m_engine->add_layer (effects_layer);
}
CommonDisplay::~CommonDisplay()
{
delete m_engine;
}
Model * CommonDisplay::set_model (const GridLoc &l, Model *m)
{
int x = l.pos.x, y=l.pos.y;
switch (l.layer) {
case GRID_FLOOR: floor_layer->set_model (x, y, m); break;
case GRID_ITEMS: item_layer->set_model (x, y, m); break;
case GRID_STONES:
stone_layer->set_model (x, y, m);
// shadow_layer->set_model (x, y, m);
// shadow_layer->update (x, y);
break;
case GRID_COUNT: break;
}
return m;
}
Model *
CommonDisplay::get_model (const GridLoc &l)
{
int x = l.pos.x, y=l.pos.y;
switch (l.layer) {
case GRID_FLOOR: return floor_layer->get_model (x, y);
case GRID_ITEMS: return item_layer->get_model (x, y);
case GRID_STONES: return stone_layer->get_model (x, y);
case GRID_COUNT: return 0;
}
return 0;
}
Model *
CommonDisplay::yield_model (const GridLoc &l)
{
int x = l.pos.x, y=l.pos.y;
switch (l.layer) {
case GRID_FLOOR: return floor_layer->yield_model (x, y);
case GRID_ITEMS: return item_layer->yield_model (x, y);
case GRID_STONES: return stone_layer->yield_model (x, y);
case GRID_COUNT: return 0;
}
return 0;
}
RubberHandle
CommonDisplay::add_line (V2 p1, V2 p2)
{
return line_layer->add_line (p1, p2);
}
SpriteHandle
CommonDisplay::add_effect (const V2& pos, Model *m)
{
Sprite *spr = new Sprite (pos, SPRITE_EFFECT, m);
return SpriteHandle (effects_layer, effects_layer->add_sprite(spr));
}
SpriteHandle
CommonDisplay::add_sprite (const V2 &pos, Model *m)
{
Sprite *spr = new Sprite (pos, SPRITE_ACTOR, m);
return SpriteHandle (sprite_layer, sprite_layer->add_sprite(spr));
}
void CommonDisplay::new_world (int w, int h) {
get_engine()->new_world (w, h);
}
void CommonDisplay::redraw() {
get_engine()->update_screen();
}
void CommonDisplay::set_floor (int x, int y, Model *m) {
floor_layer->set_model (x, y, m);
}
void CommonDisplay::set_item (int x, int y, Model *m) {
item_layer->set_model (x,y , m);
}
void CommonDisplay::set_stone (int x, int y, Model *m) {
stone_layer->set_model (x,y , m);
}
//----------------------------------------------------------------------
// Game Display Engine
//----------------------------------------------------------------------
GameDisplay::GameDisplay (const ScreenArea &gamearea,
const ScreenArea &inventoryarea_)
: CommonDisplay(gamearea),
last_frame_time (0),
redraw_everything(false),
m_reference_point (),
m_follower (0),
inventoryarea (inventoryarea_)
{
status_bar = new StatusBarImpl (inventoryarea);
}
GameDisplay::~GameDisplay()
{
delete m_follower;
delete status_bar;
}
void GameDisplay::tick(double dtime) {
get_engine()->tick (dtime);
status_bar->tick (dtime);
if (m_follower)
m_follower->tick (dtime, m_reference_point);
}
void GameDisplay::new_world (int w, int h) {
CommonDisplay::new_world (w, h);
status_bar->new_world();
resize_game_area (NTILESH, NTILESV);
set_follow_mode (FOLLOW_SCREEN);
m_reference_point = V2();
// shadow_layer->new_world(w,h);
}
StatusBar *
GameDisplay::get_status_bar() const
{
return status_bar;
}
/* -------------------- Scrolling -------------------- */
void GameDisplay::set_follow_mode (FollowMode m) {
switch (m) {
case FOLLOW_NONE:
set_follower(0);
break;
case FOLLOW_SCROLLING:
set_follower (new Follower_Scrolling(get_engine(), false));
break;
case FOLLOW_SCREEN:
set_follower (new Follower_Screen(get_engine()));
break;
case FOLLOW_SCREENSCROLLING:
set_follower (new Follower_Scrolling(get_engine(), true));
break;
case FOLLOW_SMOOTH:
set_follower (new Follower_Smooth(get_engine()));
};
}
void GameDisplay::set_follower (Follower *f) {
delete m_follower;
if ((m_follower = f))
follow_center();
}
void GameDisplay::follow_center() {
if (m_follower)
m_follower->center (m_reference_point);
}
void GameDisplay::set_reference_point (const V2 &point) {
m_reference_point = point;
}
void GameDisplay::get_reference_point_coordinates(int *x, int *y) {
get_engine()->world_to_screen(m_reference_point, x, y);
}
void GameDisplay::set_scroll_boundary (double boundary)
{
if (m_follower)
m_follower->set_boundary (boundary);
}
/* ---------- Screen updates ---------- */
void GameDisplay::redraw_all (Screen *scr) {
get_engine()->mark_redraw_screen();
redraw_everything = true;
scr->update_all();
redraw (scr);
}
void GameDisplay::redraw (ecl::Screen *screen) {
GC gc(screen->get_surface());
if (SDL_GetTicks() - last_frame_time > 10) {
CommonDisplay::redraw();
if (ShowFPS) {
char fps[20];
sprintf (fps,"fps: %d\n", int(1000.0/(SDL_GetTicks()-last_frame_time)));
Font *f = enigma::GetFont("levelmenu");
clip(gc);
Rect area (0,0,80,20);
set_color (gc, 0,0,0);
box (gc, area);
f->render (gc, 0,0, fps);
screen->update_rect(area);
}
last_frame_time = SDL_GetTicks();
}
if (status_bar->has_changed() || redraw_everything) {
status_bar->redraw (gc, inventoryarea);
screen->update_rect(inventoryarea);
}
if (redraw_everything)
draw_borders(gc);
screen->flush_updates();
redraw_everything = false;
}
void GameDisplay::draw_all (GC &gc) {
get_engine()->draw_all(gc);
status_bar->redraw (gc, inventoryarea);
draw_borders(gc);
}
void GameDisplay::draw_borders (GC &gc) {
RectList rl;
rl.push_back (gc.drawable->size());
rl.sub (get_engine()->get_area());
rl.sub (inventoryarea);
clip(gc);
set_color (gc, 0, 0, 0);
for (RectList::iterator i=rl.begin(); i!=rl.end(); ++i) {
box (gc, *i);
}
}
void GameDisplay::resize_game_area (int w, int h)
{
DisplayEngine *e = get_engine();
int neww = w * e->get_tilew();
int newh = h * e->get_tileh();
const video::VMInfo *vidinfo = video::GetInfo();
int screenw = vidinfo->width;
int screenh = NTILESV * vidinfo->tile_size;
if (neww > screenw || newh > screenh) {
enigma::Log << "Illegal screen size ("<< neww << "," << newh
<< "): larger than physical display\n";
return;
}
Rect r ((screenw-neww)/2, (screenh-newh)/2, neww, newh);
e->set_screen_area (r);
follow_center();
}
/* -------------------- Global functions -------------------- */
void display::Init() {
InitModels();
const video::VMInfo *vminfo = video::GetInfo();
gamedpy = new GameDisplay (vminfo->gamearea,
vminfo->statusbararea);
}
void display::Shutdown() {
delete gamedpy;
ShutdownModels();
}
void display::Tick (double dtime) {
gamedpy->tick(dtime);
}
StatusBar * display::GetStatusBar() {
return gamedpy->get_status_bar();
}
void display::NewWorld(int w, int h) {
gamedpy->new_world (w, h);
}
void display::FocusReferencePoint() {
gamedpy->follow_center();
}
void display::SetReferencePoint (const ecl::V2 &point) {
gamedpy->set_reference_point (point);
}
void display::SetFollowMode(FollowMode m) {
gamedpy->set_follow_mode(m);
}
void display::SetScrollBoundary (double boundary) {
gamedpy->set_scroll_boundary (boundary);
}
void display::GetReferencePointCoordinates(int *x, int *y) {
gamedpy->get_reference_point_coordinates(x, y);
}
Model *display::SetModel (const GridLoc &l, Model *m) {
return gamedpy->set_model (l, m);
}
Model *display::SetModel (const GridLoc &l, const string &modelname) {
return SetModel(l, MakeModel(modelname));
}
void display::KillModel(const GridLoc & l) {
delete YieldModel(l);
}
Model *display::GetModel(const GridLoc &l) {
return gamedpy->get_model (l);
}
Model *display::YieldModel(const GridLoc &l) {
return gamedpy->yield_model (l);
}
SpriteHandle display::AddEffect (const V2& pos, const char *modelname) {
return gamedpy->add_effect (pos, MakeModel(modelname));
}
SpriteHandle
display::AddSprite (const V2& pos, const char *modelname)
{
Model *m = modelname ? MakeModel(modelname) : 0;
return gamedpy->add_sprite (pos, m);
}
void display::ToggleFlag(DisplayFlags flag)
{
toggle_flags (display_flags, flag);
}
void display::DrawAll (GC &gc) {
gamedpy->draw_all(gc);
}
void display::RedrawAll(Screen *screen) {
gamedpy->redraw_all(screen);
}
void display::Redraw (Screen *screen) {
gamedpy->redraw (screen);
}
void display::ResizeGameArea (int w, int h) {
gamedpy->resize_game_area (w, h);
}
const Rect& display::GetGameArea () {
return gamedpy->get_engine()->get_area();
}
RubberHandle
display::AddRubber (const V2 &p1, const V2 &p2)
{
return gamedpy->add_line (p1, p2);
}