=begin
==============================================================================
** Dynamic Light & Shadows
------------------------------------------------------------------------------
Trebor777
Version 1.5
16/11/2007
Version 1.5 is based on Rataime's shadows script,Rewrote the code structure
and Added extra features
==============================================================================
Instructions
==============================================================================
To create a source light:
Write a comment on 2 lines
Light
arg1|arg2|arg3|arg4|arg5
arg1, is the minimum angle.
arg2, is the maximum angle
arg3, the distance in pixel covered by the light
arg4, the height, in tiles where the light is.
arg5, the direction of the light, use the same numbers as RMXP is using:
up: 8, right: 6, down: 2, left: 4
It will turn the event automatically to this direction and
draw the light according to it, using its min and max angle.
The 0 is always on the left of the direction.
example,
the light "look at" the right, so its direction is 6.
the min angle is 0, so it'll start from the tile above to whatever
the max angle is, in a clockwise way.
So if you need to create a light, who covers only a cone of 60?, facing to the
right, at a height of 1 tile, covering a radius of 150pixels:
Light
60|120|150|1|6
I might do in the future, a simpler version for that.
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
To have an event with a shadow:
Write a comment on 2 lines
Shadow
arg1
arg1, is the maximum height of the object, so if you have a pillar on your map
covering several tiles in height, just create a shadow event at its base,
and give the height of that pillar as arg1.
For characters, just use a height of 0.
To turn off/on a light:
Use a script call:
a simple "self.off=true" (to turn off) or "self.off=false" to turn on is needed.
What is important is where you use this script call:
If the event switching the light is not the same as the source, you need use the
"call script" command inside the "set move route" command
(of course, don't forget to say on which event it applies)
instead of the default "call script" command found on page3.
==============================================================================
Configuration
==============================================================================
==============================================================================
You probably won't need to touch this : it's the 'map' of how to display the
shadow depending on the event's direction and his relative position to the
source. a minus means the shadow is mirrored. It seems complex, and it is.
Complain to Enterbrain (why didn't they use something clockwise or counter-
clockwise ? I suspect it's because of the rm2k legacy. More explanations
below.
=============================================================================
=end
SDK.log('DL&S', "trebor777", 1, "16.11.07")
if SDK.state('DL&S')
SHADOWS_DIRECTION_ARRAY = Array.new
SHADOWS_DIRECTION_ARRAY[2] = [ -3, 4, -2, 1 ]
SHADOWS_DIRECTION_ARRAY[4] = [ 4, -2, 1, -3 ]
SHADOWS_DIRECTION_ARRAY[6] = [ 1, -3, 4, -2 ]
SHADOWS_DIRECTION_ARRAY[8] = [ -2, 1, -3, 4 ]
#==============================================================================
# An important option : if you set it to true, the shadows will get longer if
# you are far from the source. Nice, but induces lag : it will eat your CPU,
# and quite possibly your first born if you try that on a big map.
#==============================================================================
SHADOW_GETS_LONGER = true
#==============================================================================
# Misc options
# If an event has its opacity below SHADOWS_OPACITY_THRESHOLD, no shadow will
# be displayed.
# Set SHADOWS_CATERPILLAR_COMPATIBLE to true if you uses the caterpillar script
#==============================================================================
SHADOWS_OPACITY_THRESHOLD = 254
SHADOWS_CATERPILLAR_COMPATIBLE = true
#==============================================================================
# Class Light
#==============================================================================
class Light
@@lights=[]
attr_reader :character, :parameters
#--------------------------------------------------------------------------
def initialize(viewport,character, param)
bih@character = character
bih@viewport = viewport
unless param.nil?
bih@parameters = param[0].split('|')
bih@anglemin = @parameters.first.to_i
bih@anglemax = @parameters[1].to_i
bih@distancemax = @parameters[2].to_i
bih@light_height = @parameters[3].to_i
bih@base_face_at = @parameters.last.to_i
@character.direction=@base_face_at
test = @@lights.find_all{|l| l.character==bih@character and l.parameters==bih@parameters}
if test.size==0
draw_lights
update
@@lights.push(self)
end
end
end
#--------------------------------------------------------------------------
def Light.set
return @@lights
end
#--------------------------------------------------------------------------
def Light.off_size
result=0
@@lights.each do |light|
result+=1 if light.character.off
end
return result
end
#--------------------------------------------------------------------------
def dispose
bih@s_light.dispose
@@lights.delete(self)
end
#--------------------------------------------------------------------------
def update
unless @s_light.nil?
@s_light.visible=!@character.off
bih@s_light.angle=0 if @character.direction==2
bih@s_light.angle=-90 if @character.direction==4
bih@s_light.angle=180 if @character.direction==8
bih@s_light.angle=90 if @character.direction==6
@s_light.x=@character.screen_x
@s_light.y=@character.screen_y+32*@light_height
@s_light.z=@character.screen_z
end
end
#--------------------------------------------------------------------------
def draw_lights
return if @@lights.include?(self) or (@@lights.find_all{|l| l.character==bih@character and l.parameters==@parameters}).size>0
radius = @distancemax
bih@s_light=Sprite.new(@viewport)
@s_light.x=@character.screen_x
@s_light.y=@character.screen_y+32*@light_height
@s_light.z=@character.screen_z
bih@s_light.bitmap=Bitmap.new(radius*2,radius*2)
bih@s_light.opacity=90
bih@s_light.ox+=radius
bih@s_light.oy+=radius
bih@s_light.angle=0 if @base_face_at==2
bih@s_light.angle=270 if @base_face_at==4
bih@s_light.angle=180 if @base_face_at==8
bih@s_light.angle=90 if @base_face_at==6
@s_light.bitmap.draw_pie(radius,radius,radius,Color.new(255,255,100,90),@anglemi
n, @anglemax)
end
end
#==============================================================================
# Class shadow
#==============================================================================
class Shadow
attr_accessor :character
attr_reader :distance, :distancemax, :overlayed
@@shadows=[]
#--------------------------------------------------------------------------
def initialize(viewport,character, param,light)
bih@character = character
bih@viewport = viewport
unless param.nil?
bih@parameters = param[0].split('|')
bih@shadow_max_height = @parameters[1].to_i
@anglemin=light.parameters[0].to_f
@anglemax=light.parameters[1].to_f
@distancemax=light.parameters[2].to_f
bih@light_height= light.parameters[3].to_i
bih@source = light
bih@s_shadow = RPG::Sprite.new(@viewport)
bih@s_shadow.color = Color.new(0, 0, 0)
update
end
@@shadows.push(self)
end
#--------------------------------------------------------------------------
def Shadow.set
return @@shadows
end
#--------------------------------------------------------------------------
def sprite
return @s_shadow
end
#--------------------------------------------------------------------------
def dispose
kbm@s_shadow.dispose
@@shadows.delete(self)
end
#--------------------------------------------------------------------------
def in_light_range?
return (@distance<=@distancemax)
end
#--------------------------------------------------------------------------
def overlayed?
kbm@overlayed = false
@@shadows.each do |i|
s = i.sprite
next if s.nil? or i == self or @character.tile_id!=0 or s.disposed?
if (@s_shadow.z)>s.z and @s_shadow.angle.between?(s.angle-1.5,s.angle+1.5) and ((@character.x-i.character.<img src="http://rpgmkr.net/forum/public/style_emoticons/default/sourirex.gif" border="0" />**2 + (@character.y-i.character.y)**2)**0.5<=s.zoom_y and s.z>=0
@s_shadow.visible=false
@overlayed = true
end
return if !kbm@s_shadow.visible
end
return @overlayed
end
#--------------------------------------------------------------------------
def update
# set shadow visibility according to the light state
kbm@s_shadow.visible = !kbm@source.character.off
if @character.transparent or @character.opacity <= SHADOWS_OPACITY_THRESHOLD
kbm@s_shadow.visible = false
return
end
if @old_amin.nil? and @old_amax.nil? and @old_dir.nil?
kbm@old_amin= @anglemin
kbm@old_amax= @anglemax
kbm@old_dir = @source.character.direction
end
# adapt the angle according to the source direction
case @source.character.direction
when 2
@anglemin= @old_amin+180
@anglemax= @old_amax+180
when 4
@anglemin= @old_amin+90
@anglemax= @old_amax+90
when 8
@anglemin= @old_amin
@anglemax= @old_amax
when 6
@anglemin= @old_amin+270
@anglemax= @old_amax+270
end
if @old_dir!=@source.character.direction
kbm@old_dir = @source.character.direction
end
# simplify the angles if more than 360 or less than 0
@anglemin%=360;@anglemax%=360
tile_height= @shadow_max_height
@deltax=(@source.character.real_x-@character.real_X)/4
kbm@deltay= ((@source.character.real_y+@light_height*128)-(@character.real_y+tile_height*128))/4
kbm@distance = (((@deltax ** 2) + (@deltay ** 2))** 0.5)
if @distancemax !=0 and @distance>@distancemax
kbm@s_shadow.visible = false
return
end
kbm@s_shadow.angle = 57.3*Math.atan2(@deltax, @deltay )
kbm@angle_trigo= (@s_shadow.angle+90) % 360
#test if there is a shadow above it, from something close,and then
# don't display its shadow
return if overlayed?
if @anglemin !=0 or @anglemax !=0
if (@angle_trigo<@anglemin or @angle_trigo>@anglemax) and @anglemin<@anglemax
@s_shadow.visible = false
return
elsif (@angle_trigo<@anglemin and @angle_trigo>@anglemax) and @anglemin>@anglemax
@s_shadow.visible = false
return
end
end
kbm@s_shadow.update
if @tile_id != @character.tile_id or
kbm@character_name != @character.character_name or
kbm@character_hue != @character.character_hue
kbm@tile_id = @character.tile_id
kbm@character_name = @character.character_name
kbm@character_hue = @character.character_hue
if @tile_id >= 384
@s_shadow.bitmap = RPG::Cache.tile($game_map.tileset_name,
@tile_id, @character.character_hue)
@s_shadow.src_rect.set(0, 0, 32, 32)
@s_shadow.ox = 16
@s_shadow.oy = 32
else
@s_shadow.bitmap = RPG::Cache.character(@character.character_name,
@character.character_hue)
@cw = @s_shadow.bitmap.width / 4
@ch = @s_shadow.bitmap.height / 4
@s_shadow.ox = @cw / 2
@s_shadow.oy = @ch
end
end
kbm@s_shadow.x = @character.screen_x
kbm@s_shadow.y = @character.screen_y-8
kbm@s_shadow.z = @character.screen_z(@ch)-1
if @tile_id!=0 # if the sprite graphic is from the tileset
# set the Z-Index using the tileset priority settings
kbm@s_shadow.z = ($game_map.priorities[@tile_id])*32
# convert the shadow angle, into 8-directions (0-7)
direction_shadow=((@s_shadow.angle/45+45/2.0+90).round)%8
# for the middle-Top and top layers,
for i in [1, 2]
# get the tile from the converted angle
tile_around=[$game_map.data[@character.x,@character.y-1,i],
$game_map.data[@character.x-1,@character.y-1,i],
$game_map.data[@character.x-1,@character.y,i],
$game_map.data[@character.x-1,@character.y+1,i],
$game_map.data[@character.x,@character.y+1,i],
$game_map.data[@character.x+1,@character.y+1,i],
$game_map.data[@character.x+1,@character.y,i],
$game_map.data[@character.x+1,@character.y-1,i]]
# if the tile is the empty one go to the next layer
next if tile_around[direction_shadow]==0
# else, lower the Z-index if the tile around is "above" or at the same
# priority of our tile
@s_shadow.z-=32 if $game_map.priorities[tile_around[direction_shadow]]>=$game_map.priorities[@tile_id]
end
end
if @tile_id == 0
sx = @character.pattern * @cw
quarter = ((@angle_trigo/90+0.5).floor)%4
# The quarter is the position of the event relative to the source.
# Imagine the source is the o point (0,0). Trace the 2 lines
# y=x and y=-x : you get something like a big X
# On the right, quarter=0. Up, quarter = 1, and so on
# Take the @character.direction row (2,4,6,8), and the quarter
# column (0,1,2,3) (remember, it starts at 0), and you'll get
# a number between 1 and 4. It correspond to the row of the charset
# the shadow will be, and mirrored if negative.
# Yes, it isn't obvious, but I didn't find any simple operation to
# get those.
magic = SHADOWS_DIRECTION_ARRAY[@character.direction][quarter]
magic = -magic
if magic < 0
@s_shadow.mirror = true
magic = -magic
else
@s_shadow.mirror = false
end
sy = (magic-1)*kbm@ch
@s_shadow.src_rect.set(sx, sy, @cw, @ch)
end
# This is the formula of the opacity in function of the distance
# ** 2 means square
kbm@s_shadow.opacity = 1200/((@distance ** 2)/ 1000 + 6)
# This is the formula of the size in function of the distance
# The 0.75 is here so you have a size of 1:1 when next to the source.
kbm@s_shadow.zoom_y=0.75*(@shadow_max_height+1) + (@distance) / 256 if SHADOW_GETS_LONGER
end
end
#==============================================================================
# ** Zlib
#==============================================================================
module Zlib
#============================================================================
# ** Png_File
#============================================================================
class Png_File < GzipWriter
#--------------------------------------------------------------------------
# * Make PNG
#--------------------------------------------------------------------------
def make_png(bitmap, mode = 0)
# Save Bitmap & Mode
@bitmap, @mode = bitmap, mode
# Create & Save PNG
self.write(make_header)
self.write(make_ihdr)
self.write(make_idat)
self.write(make_iend)
end
#--------------------------------------------------------------------------
# * Make Header
#--------------------------------------------------------------------------
def make_header
return [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a].pack('C*')
end
#--------------------------------------------------------------------------
# * Make IHDR
#--------------------------------------------------------------------------
def make_ihdr
ih_size = [13].pack("N")
ih_sign = 'IHDR'
ih_width = [@bitmap.width].pack('N')
ih_height = [@bitmap.height].pack('N')
ih_bit_depth = [8].pack('C')
ih_color_type = [6].pack('C')
ih_compression_method = [0].pack('C')
ih_filter_method = [0].pack('C')
ih_interlace_method = [0].pack('C')
string = ih_sign + ih_width + ih_height + ih_bit_depth + ih_color_type +
ih_compression_method + ih_filter_method + ih_interlace_method
ih_crc = [Zlib.crc32(string)].pack('N')
return ih_size + string + ih_crc
end
#--------------------------------------------------------------------------
# * Make IDAT
#--------------------------------------------------------------------------
def make_idat
header = "\\x49\\x44\\x41\\x54"
data = @mode == 0 ? make_bitmap_data0 : make_bitmap_data1
data = Zlib::Deflate.deflate(data, 8)
crc = [Zlib.crc32(header + data)].pack('N')
size = [data.length].pack('N')
return size + header + data + crc
end
#--------------------------------------------------------------------------
# * Make Bitmap Data 0
#--------------------------------------------------------------------------
def make_bitmap_data0
gz = Zlib::GzipWriter.open('hoge.gz')
t_Fx = 0
w = @bitmap.width
h = @bitmap.height
data = []
for y in 0...h
data.push(0)
for x in 0...w
t_Fx += 1
if t_Fx % 10000 == 0
Graphics.update
end
if t_Fx % 100000 == 0
s = data.pack("C*")
gz.write(s)
data.clear
end
color = @bitmap.get_pixel(x, y)
red = color.red
green = color.green
blue = color.blue
alpha = color.alpha
data.push(red)
data.push(green)
data.push(blue)
data.push(alpha)
end
end
s = data.pack("C*")
gz.write(s)
gz.close
data.clear
gz = Zlib::GzipReader.open('hoge.gz')
data = gz.read
gz.close
File.delete('hoge.gz')
return data
end
#--------------------------------------------------------------------------
# * Make Bitmap Data Mode 1
#--------------------------------------------------------------------------
def make_bitmap_data1
w = @bitmap.width
h = @bitmap.height
data = []
for y in 0...h
data.push(0)
for x in 0...w
color = @bitmap.get_pixel(x, y)
red = color.red
green = color.green
blue = color.blue
alpha = color.alpha
data.push(red)
data.push(green)
data.push(blue)
data.push(alpha)
end
end
return data.pack("C*")
end
#--------------------------------------------------------------------------
# * Make IEND
#--------------------------------------------------------------------------
def make_iend
ie_size = [0].pack('N')
ie_sign = 'IEND'
ie_crc = [Zlib.crc32(ie_sign)].pack('N')
return ie_size + ie_sign + ie_crc
end
end
end
#==============================================================================
# Class Game Event
#==============================================================================
class Game_Event
alias treb_mobile_source_game_event_initialize initialize
attr_accessor :off, :direction
def initialize(*args)
# create the off attribute, only used for the lights
kbm@off = false
treb_mobile_source_game_event_initialize(*args)
end
end
#==============================================================================
# class Spriteset Map
#==============================================================================
class Spriteset_Map
alias trebor777_DLS_spriteset_map_init_characters init_characters
alias trebor777_DLS_spriteset_map_update_character_sprites update_character_sprites
alias trebor777_DLS_spriteset_map_dispose dispose
#--------------------------------------------------------------------------
def init_characters
trebor777_DLS_spriteset_map_init_characters
# create the lights and shadows arrays
kbm@lights = []
kbm@shadows = []
kbm@viewport1.visible=false
kbm@viewport2.visible=false
kbm@viewport3.visible=false
for i in $game_map.events.keys.sort
# Search for light trigger
light_param = SDK.event_comment_input($game_map.events[i], 1, "Light")
# if not found go to the next iteration
next if light_param.nil?
# else add it to the Lights Hash, and create a new light
@lights.push( Light.new(@viewport1,$game_map.events[i],light_param) )
# create the shadow for the player.
@shadows.push( Shadow.new(@viewport1,$game_player, ['0'], @lights.last) )
end
for i in $game_map.events.keys.sort
for light in @lights
# Search for shadow trigger
shadow_param = SDK.event_comment_input($game_map.events[i], 1, "Shadow")
next if shadow_param.nil?
@shadows.push( Shadow.new(@viewport1,$game_map.events[i], shadow_param, light) )
end
end
kbm@viewport1.visible=true
kbm@viewport2.visible=true
kbm@viewport3.visible=true
end
#--------------------------------------------------------------------------
def update_character_sprites
trebor777_DLS_spriteset_map_update_character_sprites
lights_off = 0
# Update the lights
for light in @lights
light.update
# count the number of lights off
lights_off+=1 if light.character.off
end
# Update the Shadows
for shade in @shadows
shade.update
end
if @lights.size>0
# updating screen tinting according to the number of lights on
value=((lights_off.to_f/@lights.size)*(-180)).round
$game_screen.start_tone_change(Tone.new(value,value,value,value), 1)
else
$game_screen.start_tone_change(Tone.new(0,0,0,0), 1)
end
end
#--------------------------------------------------------------------------
def dispose
for light in @lights
light.dispose
end
# Update the Shadows
for shade in @shadows
shade.dispose
end
kbm@lights = []
kbm@shadows = []
end
end
#==============================================================================
# Class Bitmap
#==============================================================================
class Bitmap
#--------------------------------------------------------------------------
def draw_line(x1, y1, x2, y2, width = 1, color = Color.new(255, 255, 255))
# Return if width is less than or 0
return if width <= 0
# Reverse all parameters sent if 2 x is less than the first x
x1, x2, y1, y2 = x2, x1, y2, y1 if x2 < x1
# Get S (1/2 width)
s = width / 2.0
# If X Coordinates are equal
if x1 == x2
# Draw Vertical line
fill_rect(x1 - s, [y1, y2].min, width, (y2 - y1).abs, color)
# If Y Coordinates are equal
elsif y1 == y2
# Draw Horizontal line
fill_rect(x1, y1 - s, x2 - x1, width, color)
end
# Get Length
length = x2 - x1 < (y2 - y1).abs ? (y2 - y1).abs : x2 - x1
# Get Increments
x_increment, y_increment = (x2 - x1) / length.to_f, (y2 - y1) / length.to_f
# Get Current X and Y
x, y = x1, y1
# While Current X is less than end X
while x < x2
# Draw Box of width width and width height
fill_rect(x-s, y-s, width, width, color)
# Increment X and Y
x += x_increment
y += y_increment
end
end
#--------------------------------------------------------------------------
# Draw pie, take quite a long time as it draws each line.
#--------------------------------------------------------------------------
def draw_pie(x,y,radius,color = Color.new(255, 255, 255, 255),start_angle=0,end_angle=360)
end_angle+=360 if end_angle
name_string="#{radius}_#{start_angle}_#{end_angle}"
#filename="Graphics/Pictures/#{name_string}.png"
if FileTest.exist?("Graphics/Pictures/#{name_string}.png")
temp = RPG::Cache.picture(name_string)
blt(0,0,temp,temp.rect)
else
Graphics.transition
t = Progress_Bar.new(160,240,320,20,end_angle-start_angle+2 )
for i in start_angle...end_angle
t.current_step+=1
t.update
Graphics.update
for j in 0..2
x_=(Math::cos((i+j/2.0)*Math::PI/180)*radius).round+x
y_=(Math::sin((i+j/2.0)*Math::PI/180)*radius).round+y
draw_line(x, y, x_, y_, 2,color)
end
end
t.current_step+=1
t.update
Graphics.update
make_png(name_string, 'Graphics/Pictures/')
t.current_step+=1
t.update
Graphics.update
t.dispose
end
end
def make_png(name = 'like', path = '', mode = 0)
Dir.make_dir(path) if path != '' and !FileTest.directory?(path)
Zlib::Png_File.open('temp.gz') { |gz| gz.make_png(self, mode) }
Zlib::GzipReader.open('temp.gz') { |gz| $read = gz.read }
f = File.open(path + name + '.png', 'wb')
f.write($read)
f.close
File.delete('temp.gz')
end
end
class Progress_Bar < Sprite
#--------------------------------------------------------------------------
# * Public Instance Variables
#--------------------------------------------------------------------------
attr_accessor :current_step # The Current Step
attr_accessor :steps # The max amount of steps
#----------------------------------------------------------------------------
# * Initialize Object
#----------------------------------------------------------------------------
def initialize(x,y,width,height=16,steps=100,start=0)
super()
kbm@steps = steps
self.x = x
self.y = y
kbm@current_step= start
kbm@width = width
kbm@height = height
kbm@w = 9
kbm@nb_bars = @width/@w
kbm@c1 = Color.new(46,211,49,255)
kbm@c2 = Color.new(46,211,49,227)
kbm@c3 = Color.new(46,211,49,202)
kbm@c4 = Color.new(46,211,49,177)
kbm@c5 = Color.new(46,211,49,152)
kbm@c6 = Color.new(46,211,49,127)
kbm@stretch_c = (@height-4)/16.0
self.bitmap = Bitmap.new(@width, @height)
self.bitmap.clear
self.bitmap.fill_rect(0, 0, @width, @height, Color.new(59,59,59,167))
draw
end
#----------------------------------------------------------------------------
# * Draw Bar
#----------------------------------------------------------------------------
def draw
self.bitmap.fill_rect(1, 1, @width-2, @height-2, Color.new(59,59,59,0))
for i in 0...((@current_step/@steps.to_f)*@nb_bars).round
self.bitmap.fill_rect(i*@w+1,2,@w-2,@height-4,@c6)
self.bitmap.fill_rect(i*@w+1,3*@stretch_c,@w-2,@height-6*@stretch_c,@c5)
self.bitmap.fill_rect(i*@w+1,4*@stretch_c,@w-2,@height-8*@stretch_c,@c4)
self.bitmap.fill_rect(i*@w+1,5*@stretch_c,@w-2,@height-10*@stretch_c,@c3)
self.bitmap.fill_rect(i*@w+1,6*@stretch_c,@w-2,@height-12*@stretch_c,@c2)
self.bitmap.fill_rect(i*@w+1,7*@stretch_c,@w-2,@height-14*@stretch_c,@c1)
end
kbm@old = @current_step
end
#----------------------------------------------------------------------------
# * Update
#----------------------------------------------------------------------------
def update
draw if @current_step != @old
end
end
#SDK test
end